Freigeben über


Generalize Smart Pointers in C++

After programming in C++ for a while, you will inevitably be introduced to the concept of “smart pointers” (or you will discover them on your own). These are pointers that know to automatically release the objects they point to when they (the pointers) are destructed. They have proved quite handy in avoiding memory leaks.

The most well known implementation of smart pointers probably is auto_ptr in STL and CAutoPtr in ATL, which will delete the object upon destruction.

auto_ptr and CAutoPtr are fine, but they have limitations:

  1. Sometimes the pointer is not allocated via the new operator (e.g. malloc()). In these cases, you don’t want to use auto_ptr or CAutoPtr as the pointer will by wrongfully deleted (as opposed to be free()-ed in the case of malloc()).
  2. They are good for wrapping pointers only. In general, you would like to allocate some resource and have the compiler release it for you automatically.

Why not generalize smart pointers to smart resource managers? It’s actually easy to do so. Here’s my take on this:

///////////////////////////////////////////////////////////

// Class CAutoResource

//

// An object of this template class holds a resource, and will dispose of

// the resource when it is destructed. This class is a generalization of

// auto_ptr in STL and CAutoPtr in ATL. The user must supply the function

// doing the disposition as a template parameter.

//

// Examples where this class can be useful include file/window/registry

// handles and pointers.

template< typename T, void (*Dispose)( T ), const T NullResource = 0>

class CAutoResource

{

public:

    typedef T ResourceType;

      CAutoResource( T Resource = NullResource )

            : m_Resource( Resource )

      {

      }

    ~CAutoResource()

    {

        if ( NullResource != m_Resource )

        {

            Dispose( m_Resource );

        }

    }

    operator T() const

    {

        return m_Resource;

    }

    T Get() const

    {

        return m_Resource;

    }

    void Attach( T NewResource )

    {

        Dispose( m_Resource );

   m_Resource = NewResource;

    }

      // The assert on operator & usually indicates a bug.

    T * operator & ()

    {

        _ASSERTE( NullResource == m_Resource );

        return & m_Resource;

    }

private:

    T m_Resource;

    CAutoResource( const CAutoResource& );

    CAutoResource& operator = ( const CAutoResource& );

};

To use the CAutoResource template class, you need to supply:

  1. The type of the resource;
  2. A function that disposes of the resource; and
  3. (Optional) The value that represents the “null” resource. If you don’t specify it, 0 is used.

Upon destruction, the resource in a CAutoResource object will be released by calling the resource disposition function.

I hope it becomes obvious to you that auto_ptr and CAutoPtr are basically special cases of CAutoResource where the disposition function is the delete operator. They do have more member functions, but those are not hard to add.

Let’s see some examples of using CAutoResource.

The first one is to wrap the generic Windows HANDLE, which is used by the Win32 API to represent a lot of different things (windows, fonts, brushes, memory, and etc). A typedef instantiates CAutoResource for this scenario:

#include <windows.h>

// Generic Windows handle

void DisposeHandle( HANDLE handle )

{

   if ( NULL != handle )

    {

        CloseHandle( handle );

    }

}

typedef CAutoResource< HANDLE, DisposeHandle > CAutoHandle;

Our second example is handle to a registry key:

// Handle to registry key

void DisposeHKey( HKEY hKey )

{

    if ( NULL != hKey )

    {

        RegCloseKey( hKey );

    }

}

typedef CAutoResource< HKEY, DisposeHKey > CAutoHKey;

Sometimes, two resources have the same C++ type but different semantics and should be released in different manners. For example, an HINTERNET should be released by either WinHttpCloseHandle() or InternetCloseHandle(), depending on whether it was allocated using the WinHttp or the WinINet API. This is no problem for us, as the disposition function is a type parameter to CAutoResource and you can specify different functions for the same C++ type:

#include <winhttp.h>

// Internet handle opened via the WinHttp API

void DisposeWinHttpHandle( HINTERNET handle )

{

    if ( NULL != handle )

    {

        WinHttpCloseHandle( handle );

    }

}

typedef CAutoResource< HINTERNET, DisposeWinHttpHandle > CAutoHWinHttp;

#include <wininet.h>

// Internet handle opened via the WinINet API

void DisposeHInternet( HINTERNET handle )

{

    if ( NULL != handle )

    {

        InternetCloseHandle( handle );

    }

}

typedef CAutoResource< HINTERNET, DisposeHInternet > CAutoHInternet;

Let’s give one example where the resource we want to manage is a pointer:

// Memory allocated via LocalAlloc() or LocalReAlloc()

void DisposeLocalMem( LPVOID lpMem )

{

    if ( NULL != lpMem )

    {

        LocalFree( lpMem );

    }

}

typedef CAutoResource< LPVOID, DisposeLocalMem > CAutoLocalMem;

So far so good, but as soon as you try to dereference a pointer masqueraded as a CAutoResource, you’ll find we haven’t defined the dereference operator (*) and the arrow operator (->) yet. This is because they don’t make sense in the context of general resources.

What we can do is to define these operators in a sub-class of CAutoResource. I would’ve called it CAutoPtr, but that name is taken, so instead we have CAutoPtrEx:

///////////////////////////////////////////////////////////

// Class CAutoPtrEx

//

// An object of this template class holds a pointer, and will dispose of

// the object pointed to when it is destructed. This class is a

// generalization of auto_ptr in STL and CAutoPtr in ATL.

// It is preferred to auto_ptr and CAutoPtr when 'delete' is not the right

// way to dispose of the object. The user must supply the function doing

// the disposition as a template parameter.

template< typename T, void (*Dispose)( T * ) >

class CAutoPtrEx : public CAutoResource< T *, Dispose, NULL >

{

public:

    CAutoPtrEx( T * ptr = NULL )

        : CAutoResource< T *, Dispose >( ptr )

    {}

    T& operator * () const

    {

        return *Get();

  }

    T * operator -> () const

    {

        return Get();

    }

private:

    CAutoPtrEx( const CAutoPtrEx& );

    CAutoPtrEx& operator = ( const CAutoPtrEx& );

};

I’ll finish this article with an example using CAutoPtrEx:

// Memory allocated via CoTaskMemAlloc() or CoTaskMemRealloc()

template <typename T>

class CComMem

{

public:

    static void Dispose( T * pMem )

    {

        if ( pMem )

        {

            ::CoTaskMemFree( pMem );

        }

    }

    typedef CAutoPtrEx< T, CComMem::Dispose > AutoPtr;

};

(This posting is provided "AS IS" with no warranties, and confers no rights.)

Comments

  • Anonymous
    August 06, 2004
    I like making the common case easy. Writing a Dispose() function (a Global one at that!! eeeks!) is a pain. So this is what I'd do different:

    //Provide a default for the Dispose() function
    template< typename T, void (__stdcall *Dispose)( T )=0, const T NullResource = 0>
    class CAutoResource
    {
    ........
    ~CAutoResource()
    {
    if ( NullResource != m_Resource )
    {
    if (Dispose)
    Dispose( m_Resource );
    else delete m_Resource; //default to delete
    }
    }
    ........
    };


    I tried to do something like this:

    template< typename T, void (__stdcall *Dispose)( T )=operator delete, const T NullResource = 0>

    but this wouldn't compile. Can this be made to work ?
  • Anonymous
    August 06, 2004
    Vatson,

    CAutoResource is not the right place to have a default delete function, as the resource may not be a pointer at all. CAutoPtrEx is what you want.

    'operator delete' is not accepted as a function pointer. You can define a template function like this:

    template < typename T >
    void Delete( T * p )
    {
    delete p;
    }

    Then you can do:

    template < typename T, void (* Dispose)( T * ) = Delete >
    class CAutoPtrEx
    {
    ...
    };
  • Anonymous
    August 06, 2004
    Problem with your generic handle implementation;

    It doesn't work for File handles.

    Easily fixed though; just replace the Null value with INVALID_HANDLE_VALUE instead of 0.
  • Anonymous
    August 06, 2004
    Simon,

    There is no problem for file handles. I made the value denoting null resource a template parameter just for this purpose. You just need to do something like:

    typedef CAutoResource< int, close, INVALID_HANDLE_VALUE > CAutoFileHandle;

    assuming close() closes a file handle, and the concrete type for file handle is int.
  • Anonymous
    August 06, 2004
    Rather than use a pointer to a function for releasing the resource, use functors:

    // Virtual base class for release functor
    class CAutoRelease
    {
    public:
    virtual void operator()(void *) = NULL;
    };

    // Changes to CAutoResource:
    template<typename T, const T NullResource = 0>
    class CAutoResource
    {
    public:
        typedef T ResourceType;
          CAutoResource( CAutoRelease &Release, T Resource = NullResource )
                : m_Resource( Resource ),
    m_Release(Release) {};
    ~CAutoResource()
    {
    if (NullResource != m_Resource)
    m_Release(m_Resource);
    };

    [...]

    private:
    T m_Resource;
    CAutoRelease &m_Release;
    };

    // Templated class for pointer to a type
    template <class T>
    class CAutoReleasePtr
    {
    public:
    virtual void operator()(void *p)
    {
    T pt = static_cast<T>(p);
    delete pt;
    ];
    };

    // Templated class for pointer to a COM interface
    template <class T>
    class CAutoReleaseInterface
    {
    public:
    virtual void operator()(void *p)
    {
    T *pi = static_cast<T *>(p);
    pi->Release();
    };
    };

    While CAutoResource desn't have a default constructor, any subclass can have a default constructor that passes in the appropriate release functor:

    // Template CAutoResource class for COM interfaces
    template <class T>
    class CAutoCOMPtr : public CAutoResource
    {
    typedef T *PInterface;
    CAutoCOMPtr() : CAutoResource(CAutoReleaseInterface<PInterface>, NULL) {};

    [...]
    };

    Of course, caveats apply since I've not compiled or run any of the above code, but it shouldn't be difficult to get it to work.

    Rick
  • Anonymous
    August 06, 2004
    Actually, there's a problem with the type conflict between the typedef given in CAutoResource and the operator() in the CAutoRelease virtual base class, so the idea needs a bit more work to be fleshed out completely. Nonetheless, I'd still look for a functor-based solution rather than use a pointer to function.

    Rick
  • Anonymous
    August 06, 2004
    There is one feature that std::auto_ptr has that your class doesn't. And that is the ability to return auto_ptr from a function.

    You'd have to implement the copy constructor for that.
  • Anonymous
    August 08, 2004
    What's the advantage of rewriting std::auto_ptr (and, when you decide you need more functionality, boost::shared_ptr) when you can simply wrap your resource in a class and use the existing smart pointers?

    Or, what's the advantage of making the Dispose function a template parameter instead of defining its behavior with a virtual destructor?
  • Anonymous
    August 08, 2004
    With boost::shared_ptr you can get the same functionality and even better as you will use a class soon to be part of the new C++ standard!

    Copy paste from shared_ptr documentation:

    shared_ptr<IWhatever> make_shared_from_COM(IWhatever * p)
    {
    p->AddRef();
    shared_ptr<IWhatever> pw(p, mem_fn(&IWhatever::Release));
    return pw;
    }

    Note how the resource is returned from a function ...
  • Anonymous
    August 19, 2004
    Rick,

    Good point. I'll consider using functor as opposed to function pointer.

    David & Dee,

    Thanks for pointing me to shared_ptr. Why do we need CAutoResource when we can wrap the resource in a class and use existing smart pointers? The answer is that we don't want the resource to behave like a pointer. i.e. we want to say 'resource' instead of '*resource'.