Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Generalizing the Concepts Behind auto_ptr


August 2001/Generalizing the Concepts Behind auto_ptr

Generalizing the Concepts Behind auto_ptr

Cristian Vlasceanu

The C++ auto_ptr template can plug a memory leak — too bad that’s not the only possible “leak.” Here’s a template that can close files, release Windows handles, and anything else you tell it how to do.


In my father’s memory.

Introduction

The utility auto_ptr class in the Standard C++ library was designed to encourage programmers to use the “resource acquisition is initialization” [1] technique. The technique basically consists of using automatic (or implicit) ownership of resources rather than explicitly acquiring and releasing them. The owner of a resource is the entity responsible for releasing it.

Whenever the programmer writes delete statements, memory ownership is explicit. By wrapping up plain (naked) pointers into auto_ptrs, the ownership becomes implicit. An auto_ptr declared on the stack is destroyed when the scope of its declaration is exited, and the destructor of auto_ptr automatically calls delete on the owned pointer. The programmer does not write explicit delete statements.

Memory is often used as a prominent example when the term “resource” is brought up, while other types of resources are often overlooked. These include file handles, kernel objects, graphical artifacts (such as found in the Windows GDI), and more. Although the handles [2] to these resources are often in fact pointers, this is not always the case. Even if they are pointers, they may be acquired and released using specific API calls (e.g. CreateWidget, DestroyWidget) rather than by using the new and delete operators. Therefore, auto_ptr cannot be used with generic resource handles.

This article shows the design and implementation of auto_handle, a template class that generalizes the ideas behind auto_ptr. Since it contains no hard-coded call of the delete operator, auto_handle can be used with opaque handles as well as with pointers; hence it can be used with a broader category of resources than just memory. This class is named auto_handle because it deals with the general concept of “handle to resource.”

Intended Use

In the following example, the FILE pointer cannot be wrapped in an auto_ptr.

// returns the length of a file
long int file_len(const string& fname)
{
    FILE* fp = fopen(fname.c_str(), "r");
    if (!fp)
    {
        throw "could not open file";
    }
    if (fseek(fp, 0, SEEK_END) == -1)
    {

        // a leak occurs here: the FILE 
        // object is never closed
        throw "fseek error";
    }
    long int len(ftell(fp));
    fclose(fp);
    return len;
}

The programmer has nothing but his own discipline to rely upon in order to make sure that all open files are closed when no longer needed. Unfortunately, a missing fclose call cannot be detected or generated by means of compiler wizardry.

It would be nice to have an auto_handle class, modeled after auto_ptr, that would automatically take care of closing the FILE *. With such a class the file_len function could be rewritten as follows:

long int file_len(const string& fname)
{
    auto_handle<FILE*> fp
        (fopen(fname.c_str(), "r"));
    if (!fp)
    {
        throw "could not open file";
    }
    if (fseek
        (fp.get(), 0, SEEK_END) == -1)
    {
        // no leak: fp goes out of scope
        // and the destructor closes the
        // FILE
        throw "fseek error";
    }
    return ftell(fp.get());
}

The auto_handle class presented here enables just this sort of usage. Using auto_handle, the programmer can now concentrate on the big picture and not worry about the details of matching all fopen calls with fclose calls. Such housekeeping details are automatically taken care of; when file_len returns, auto_handle goes out of scope and its destructor closes the file. (An explanation of how the auto_handle destructor is “programmed” to close the file is given later.)

Copy Construction and Assignment Issues

The auto_handle class does not impose a specific behavior for copy construction or assignment [3]. Possible behaviors include (but are not limited to):

  • exclusive ownership as in auto_ptr
  • reference counting
  • cloning of resources
  • forbidding copy construction and assignment altogether

The simplest way to deal with copy construction and assignment is to forbid them altogether by declaring both operators private. This works fairly well and saves design and coding effort when there is no need for using auto_handle with, say, standard containers. A serious shortcoming of such an implementation is the impossibility of returning auto_handles from functions.

Return by Value

There are workarounds for this shortcoming, and the class below demonstrates the mechanism used by auto_handle to support return by value. It does not require a public copy constructor.

class non_copyable
{
public:
    non_copyable() {}
    
    struct ref
    { explicit ref(int){} };

    non_copyable(const ref&) {}
 
    // conversion operator   
    operator ref() const
    { return ref(42); }
private:
    // non-const reference on purpose
    non_copyable(/*const*/ non_copyable&);
};

non_copyable func()
{
    return non_copyable();
}

int main()
{
    non_copyable a;

    // does not compile, illegal
    // access to private ctor:

    // non_copyable b(a);

    // does compile!
    non_copyable c(func());

    return 0;
}

The copy constructor (disabled) takes a non-constant reference. Because the function func returns a temporary variable, the compiler does not employ the copy constructor (since the temporary variable needs to be bound to a const reference). Instead, the compiler uses the constructor that takes a const reference to a ref class, and the code compiles just fine, thanks to the conversion operator [4]. The auto_handle class employs this technique to support returning auto_handles by value from functions.

Implementing auto_handle

Looking at the version of file_len written with auto_handle, you can see that the concrete type of the resource (FILE*) is used as a template argument. Although it isn’t obvious, you may have deduced that how the resource is released (via fclose) also depends on the resource type. That is, the compiler should be able to determine what function to call in the destructor of auto_handle from the type of the template argument. The following code demonstrates how traits are used in order to help the compiler make the determination.

template<class H>
struct handle_traits
{
    static void dispose(H) throw();
};

template<class H,
    class T = handle_traits<H> >
class auto_handle
{
public:
    // 20.4.5.1 construct/copy/destroy     
    // ctors snipped out...

    ~auto_handle() throw()
    {
        T::dispose(handle_);
    }
    // more code removed...

private:
    H handle_;
};

The auto_handle template class has a second (default) parameter that associates a disposal method with a concrete handle type. (Note that the dispose method must not throw exceptions because it is called from a destructor.)

The following specialization is needed to make the file_len function work:

template<> void
handle_traits<FILE*>::dispose(FILE* fp) throw()
{
    fclose(fp);
}

The constructor of auto_ptr, as specified by section 20.4.5.1 of the C++ Standard, takes a null pointer as its default parameter. Following the same model, auto_handle should have a constructor that takes a null handle as default parameter. What is a null handle? The correct answer, of course, is “it depends.” It can be zero, but not necessarily. It could be 0xffffffff as well. The natural place for specifying this dependency is in the handle_traits template:

template<class H>
struct handle_traits
{
    static void dispose(H) throw();
    static H null_value();
};

template<class H, class T = handle_traits<H> >
class auto_handle
{
public:
    // 20.4.5.1 construct/copy/destroy
    explicit auto_handle(
        const H& h = T::null_value()) throw()
    : handle_(h) {}    

    ~auto_handle() throw()
    {
        T::dispose(handle_);
    }
    
    // 20.4.5.2 members
    H get() const throw()
    { return handle_; }
    
    // more code removed...

private:
    H handle_;
};

The program containing the file_len function example needs one more specialization in order to link:

template<> FILE*
handle_traits<FILE*>::null_value()
{
    return 0;
}

Copy Construction and Assignment, Part Two

There are enough building blocks so far for a working, minimal implementation of auto_handle. Such a minimal version would not allow copying and assignment. Returning auto_handles from functions can be arranged as described earlier.

It would be nice, however, to have support for copy construction and assignment, if needed. The handle traits introduced a level of flexibility regarding the means for releasing resources. The copy-assignment behavior should not depend on the traits. If it did, then the dispose method would have to be written again and again (most likely unchanged) every time a new copy behavior was needed.

In order for the copy behavior to be orthogonal to the handle traits, I add a third template argument, and have auto_handle inherit from it:

template<class H,
    class T = handle_traits<H>,
    class B = base_handle>
class auto_handle : private B
{
    // ...
};

To simplify usage of auto_handle, the third template argument has a default value, which implements the simplest copy assignment money can buy — that is, no copy, no assignment. (There’s no free lunch, remember?)

// default auto_handle base class:
// copy ctor is disabled
class base_handle
{
public:
    base_handle() {}
private:
    // non-const ref on purpose
    base_handle(base_handle&);
    base_handle& operator=(
        base_handle&);
};

In the following sections I describe several possible implementations for copy constructor/assignment behavior — reference counting, exclusive ownership, and cloning.

Reference Counting

In the straw man auto_handle class shown above, the destructor calls the dispose method of the handle_traits template. To implement reference counting, an extra level of indirection is needed because destruction of the auto_handle does not necessarily imply destruction of the resource. This can be achieved by making auto_handle’s destructor call a dispose method in the base class (the third template argument). The default base_handle class supports this as follows:

// default auto_handle base class
// copy ctor is disabled
template<class H, class T>
class base_handle
{
public:
    base_handle() {}

    // default disposal: forward to
    // handle_traits
    static void dispose(H h) throw()
    { T::dispose(h); }

    // place holder, in case we need
    // to implement copy construction
    static H copy(H& h) throw()
    { return h; }

private:
    // non-const references, so that
    // return by value can be implemented
    base_handle(base_handle&);
    base_handle& operator=(
        base_handle&);
};

To implement reference counting, an alternative base class needs to be defined as follows:

// A reference counted class,
// can be used as alternate base
// of auto_handle (instead of
// base_handle)
template<class H, class T = handle_traits<H> >
class ref_counted
{
public:
    ref_counted()
        : pCount_(new unsigned int(1))
    {}
    ref_counted(const ref_counted& that)
        : pCount_(that.pCount_)
    { ++*pCount_; }
    ~ref_counted()
    {
        if (--*pCount_ == 0)
            delete pCount_;
    }

    // disposes the handle if this is
    // the only instance left that owns it
    void dispose(H h) throw()
    {
        if (*pCount_ == 1)
            T::dispose(h);
    }
    // some code removed...
private:
    unsigned int* pCount_;
};

The implementation of ref_counted shows why the auto_handle destructor does not directly call traits::dispose.

Clients can now define a reference counted auto_handle type as follows:

typedef auto_handle<handle_type, handle_traits,
    ref_counted<handle_type, handle_traits> > refcnt_handle;

Exclusive Ownership

Since auto_handle was introduced as a generalized auto_ptr, you would expect to be able to implement auto_ptr in terms of auto_handle. The code below implements a base class that provides exclusive ownership:

// avoid name clashing
namespace nonstd
{
// custom base class for auto_handle
template<class H, class T = handle_traits<H> >
class exclusive_owner
{
public:
    exclusive_owner() {}
    exclusive_owner(exclusive_owner&) {}
    
    static void dispose(H h) throw()
    { T::dispose(h); }

    // transfers the handle
    static H copy(H& h)
    {
        H handle(h);

        h = T::null_value();
        return handle;
    }
};

Notice that the copy method transfers ownership of the resource rather than just copying it. In order for this copy method to be called, the copy constructor for auto_handle must be written as follows:

auto_handle(auto_handle& that) throw()
        : B(that),
        handle_(B::copy(that.handle_)) {}

The rest of the code necessary to support auto_ptr is shown here:

template<class T>
struct ptr_traits
{
    static T* null_value()
    { return 0; }
    
    static void dispose(T* pt)
    { delete pt; }
};

template<class T>
class auto_ptr : private auto_handle<
    T*, ptr_traits<T>,
    exclusive_owner<T*, ptr_traits<T> > >
{
typedef auto_handle<T*, ptr_traits<T>,
    exclusive_owner<T*, ptr_traits<T> > >
    base_type;

public:
    using base_type::get;

    explicit auto_ptr(T* pt = 0)
    : base_type(pt) {}

    auto_ptr(auto_ptr& that)
    : base_type(that) {}

    // return-by-value support:
    auto_ptr(const
        auto_handle_ref<T*>& pt)
    : base_type(pt) {}

    operator auto_handle_ref<T*> ()
    { return base_type::
        operator auto_handle_ref<T*>(); }

    T* operator->() { return get(); }
    T operator*() { return *get(); }
    
    const T* operator->() const
    { return get(); }
    
    T operator*() const
    { return *get(); }
};
} // namespace nonstd

Cloning

Some clients may require that the underlying resource be duplicated when an auto_handle is copied. This is called “cloning.” For this purpose I have added a “clone” method to the handle_traits class introduced earlier. It supports the implementation of copy constructors that rely on specific APIs to duplicate a resource. The following Windows-specific example demonstrates the cloning mechanism. The DuplicateHandle function from the Windows SDK (Software Development Kit) duplicates an object handle (in this case, an actual Windows “handle,” in the Windows meaning of the term — it is the “resource” controlled by the auto_handle). The duplicate Windows handle refers to the same object as the original handle.

template<> HANDLE
handle_traits<HANDLE>::null_value()
{
    return INVALID_HANDLE_VALUE;
}

template<> void
handle_traits<HANDLE>::dispose(HANDLE h)
{
    ::CloseHandle(h);
}

// duplicates a HANDLE in the space
// of the current Win32 process
template<> HANDLE
handle_traits<HANDLE>::clone(HANDLE h)
{
    HANDLE hProc(::GetCurrentProcess());
    HANDLE hDup(h);

    if (!::DuplicateHandle(hProc, h,
        hProc, &hDup, 0, false,
        DUPLICATE_SAME_ACCESS))
    {
        // do error handling
    }
    return hDup;
}

A class called cloner can be used as a base class for auto_handle, as follows:

template<class H,
    class T = handle_traits<H> >
class cloner
{
public:
    cloner() {}
    cloner(const cloner&) {}
    
    static void dispose(H h) throw()
    { T::dispose(h); }

    static H copy(H h)
    { return T::clone(h); }
};

typedef auto_handle<HANDLE,
    handle_traits<HANDLE>,
    cloner<HANDLE> > clonable_handle;

clonable_handle implements the “resource acquisition is initalization” idiom, as advertised. The destructor calls the dispose method of cloner, which in turn calls the specialized handle_traits dispose method, which closes the Windows handle.

Copying is allowed, and is based on the operating system’s capability for duplicating Windows handles. It is worth noting that the support for cloning is provided by the cloner class, and is independent of the handle_traits template.

Examples: Using auto_handle with the Windows GDI

The following example shows possible uses of auto_handle with the Windows GDI (a subsystem which is very sensitive to resource leaks). In the first example the handle traits are implemented as a non-template class, because HGDIOBJ is defined as pointer to void, as is the Windows HANDLE type:

struct gdi_traits
{
    // Specializing the handle_traits template
    // for HGDIOBJ won’t work. Both HANDLE and
    // HGDIOBJ are defined as pointers to void
    static HGDIOBJ null_value()
    // GDI functions return NULL when they fail
    { return 0; }
    
    static void dispose(HGDIOBJ hObj)
    { ::DeleteObject(hObj); }
};

typedef auto_handle<HGDIOBJ,
    gdi_traits> gdi_handle;

In the second example, the handle_traits template can be safely specialized for HBRUSH when strict type definitions are enforced at compile time:

#ifdef STRICT
template<> inline HBRUSH
handle_traits<HBRUSH>::null_value()
{ return 0; }

template<> inline void
handle_traits<HBRUSH>::dispose(HBRUSH h)
{ ::DeleteObject(h); }

template<> inline HBRUSH
handle_traits<HBRUSH>::clone(HBRUSH h)
{
    LOGBRUSH lb;
    
    if (!::GetObject(h, sizeof lb, &lb))
    {
        return 0;
    }
    return ::CreateBrushIndirect(&lb);
}

typedef auto_handle<HBRUSH,
    handle_traits<HBRUSH>,
    cloner<HBRUSH> > auto_brush;
#endif // STRICT

The moral is that the concrete data type of the “handle” needs to be carefully examined before specializing auto_handle.

Implementation

The complete auto_handle implementation is shown in Listing 1. Comments indicate what sections of the Standard (describing auto_ptr) were used as guides.

The implementation of the auto_handle class does not use template members (unlike the auto_ptr specs) because template members in template classes are not widely supported by existing compilers.

The code was developed with MSVC 6.3 (partially) and Code Warrior Pro 5.0.

Conclusion

auto_handle is a powerful utility class that can be used with broad categories of resources, such as system handlers, graphical resources, and plain pointers.

Acknowledgments

Thanks to Andrew Koenig, Herb Sutter, and Andrei Alexandrescu for their insightful comments and suggestions. Last but not least, many thanks to Bob Archer for reviewing, correcting the code, and encouraging me to put this paper together.

Notes and References

[1] Bjarne Stroustrup. The C++ Programming Language, Third Edition (Addison-Wesley, 1997), Chapter 14.4. Also at <http://www.research.att.com/~bs/3rd.html>.

[2] The term handle as used here does not refer to the Windows HANDLE data type.

[3] Copy and assignment can be treated together, since a very elegant and exception-safe way of implementing assignment is in terms of the copy constructor. See Herb Sutter, Exceptional C++, 47 Engineering Puzzles, Programming Problems, and Solutions (Addison-Wesley, 1999), p. 47.

[4] The code fails to compile with MSVC 6.0 SP3 due to insufficient compiler support.

[5] Scott Meyers. “auto_ptr update,” <http://www.awl.com/cseng/titles/0-201-63371-X/auto_ptr.html>.

Cristian Vlasceanu is a C++ programmer with Amazon.com in Seattle. Previously, he worked at RealNetworks, Inc. where he was responsible for developing software for portable MP3 players. His interests include generic programming and cross-platform development.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.