[cppx] Ownership with custom deleter.

The cppx Ownership class is like std::auto_ptr with custom deleter and without implicit pointer conversions. In my next posting I’ll show how to use it to simplify dealing with Xerces strings, in an efficient way. It’s also convenient for many other things. 🙂

The C++98 ownership transfer trick

A practically essential feature of a C++ ownership transfer class such as std::auto_ptr or progrock::cppx::Ownership is that it implicitly transfers ownership from temporaries such as function results.

Unfortunately C++ has two rules that, until C++0x, make it less than straightforward to implement such implicit ownership transfer from a temporary:

  • A temporary (technically, an rvalue) can’t be bound to a reference to non-const; ref. C++98 §8.5.3/5.
  • A user-defined conversion to T&, that is, an operator T&(), is ignored for an object of type T; ref. C++98 §12.3.2/1.

If not for this then one could outfit an ownership transfer class T with a T( T& other ) copy constructor. There’s intentionally no const here; it’s still a copy constructor, by C++98 §12.8/2. This reference-to-non-const copy constructor could then meddle with the other object to transfer ownership of the contained pointer, but the first point above says that no temporary will be bound directly to the other argument, and the second point above says that even if T provides a conversion to T& that conversion is ignored, since it converts to the “same” type.

So, how does e.g. std::auto_ptr manage to do this, then?

Well, as it happened I illustrated this already in my second blog posting, where I checked out WordPress’ syntax highlighting:

Using indirection to meddle with a temporary as constructor argument:

#include <iostream>
#include <memory>

class Foo
{
private:
    struct Ref
    {
        Foo*    p;
        Ref( Foo* f ): p( f ) {}
        operator Foo& () const { return *p; }
    };

    Foo( Foo & );

public:
    Foo() {}
    Foo( Foo::Ref ) { std::cout << "Hey" << std::endl; }
    operator Ref () { return this; }
};

int main()
{
    Foo     a;
    Foo     b( (Foo()) );
}

Instead of providing a conversion to Foo&, which would be ignored, Foo provides a conversion to Foo::Ref. This conversion is then implicitly invoked on the temporary actual constructor argument, passing over a pointer to that object. It’s pretty neat! 🙂

A declaration of a reference-to-non-const copy constructor is still required to prevent the compiler from supplying an ordinary copy constructor, which then would be a better match than the constructor requiring a conversion.

The reference-to-non-const copy constructor could in principle also be implemented and made public, in order to deal with lvalue arguments. And that’s what std::auto_ptr does. However, transferring ownership from one variable to another means that you’ll have at least one variable, both before and after, that does not have ownership. It can lead to confusion and bugs. So for cppx::Ownership I chose to require from-lvalue transfers to be explicitly requested via a transfer method.

Custom deleters

std::auto_ptr will always delete via a simple delete expression. This means that it can’t safely be used for arrays, and that it can’t be used when a custom deletion (such as for Xerces strings) is needed. A custom deleter specified as a constructor argument, with reasonable default, solves that, and in addition means that a cppx::Ownership<T> can be declared with incomplete type T.

A cppx::Ownership custom deleter must be a freestanding function. I figured that if more general deleters turned out to be needed then I could always change over from raw function pointers to functor objects à la C++0x std::function. But so far that generality has not been needed.

The default deleter uses std::auto_ptr to delete, in order to support classes that have protected or private destructor but befriends std::auto_ptr. C++98 has no other standard library “standard deleter”, although C++0x will have std::default_delete. In my cppx library the two common deleters – one that uses std::auto_ptr and one that does nothing – are provided by

In file [progrock/cppx/pointers/deleter.h]:

namespace progrock{ namespace cppx{

    template< class Type >
    struct Deleter
    {
        typedef Type    T;
        typedef void (*Func)( Type* );

        static void invoke( Func f, Type const* p )
        {
            f( const_cast< Type* >( p ) );
        }
    };

    enum NoDelete { noDelete };

    namespace deleter {
        // MSVC 7.1 doesn't always like e.g. "&deleter::viaAutoPtr<Whatever>".
        // So, classes.
        //
        // Placing them in a namespace allows extension for special types like
        // ConstructorArgForwarder.

        template< class Type >
        struct ViaAutoPtr
        {
            static void func( Type* p ) { std::auto_ptr< Type >( p+0 ); }
        };

        template< class Type >
        struct None
        {
            static void func( Type* ) {}
        };
    }  // namespace deleter

} }  // namespace progrock::cppx

In addition the ConstructorArgForwarder class has a special associated deleter in order to cope with deletion via a base (wrapped) class pointer:

In file [progrock/cppx/arguments/ConstructorArgForwarder.h]:

namespace progrock { namespace cppx {

    namespace deleter {

        template< typename Type >
        struct ForConstructorArgForwarderWrapped
        {
            static void func( Type* p )
            {
                delete static_cast< ConstructorArgForwarder< Type >* >( p );
            }
        };
    }

} }  // namespace progrock::cppx

The Ownership class

In order to fully understand the below code for cppx::Ownership it may help to have read my posting about constructor arguments forwarding.

In file [progrock/cppx/pointers/Ownership.h]:

namespace progrock{ namespace cppx{

    template< class Type >
    class Ownership
    {
    public:
        typedef Type                            Pointee;
        typedef typename Deleter< Type >::Func  DeleterFunc;

    private:
        Pointee*            myPtr;
        DeleterFunc         myDeleterFunc;

        struct Ref
        {
            Ownership*  p;
            Ref( Ownership* o ): p( o ) {}
        };

        Ownership( Ownership& );                    // No such.
        // If compiler reports 'inaccessible': use an explicit 'transfer' method call to
        // transfer ownership from an lvalue Ownership (this is by design).

        Ownership& operator=( Ownership& );         // No such.

    public:
        explicit Ownership(
            Type*           p,
            DeleterFunc     d = &deleter::ViaAutoPtr< Pointee >::func   // Not 0.
            )
            : myPtr( p )
            , myDeleterFunc( d )
        {
            assert( d != 0 );
        }

        Ownership( Type* p, NoDelete )
            : myPtr( p )
            , myDeleterFunc( &deleter::None< Pointee >::func )
        {}

        Ownership( MakeNew )
            : myPtr(
                new ConstructorArgForwarder< Pointee >( EmptyArgPack() )
                )
            , myDeleterFunc(
                &deleter::ForConstructorArgForwarderWrapped< Pointee >::func
                )
        {}

        template< class ArgPack >
        Ownership( MakeNew, ArgPack const& args )
            : myPtr(
                new ConstructorArgForwarder< Pointee >( args )
                )
            , myDeleterFunc(
                &deleter::ForConstructorArgForwarderWrapped< Pointee >::func
                )
        {}

        operator Ref() { return this; }             // For temporaries.
        Ref transfer() { return this; }             // For lvalues.

        Ownership( Ref r )
            : myPtr( 0 )
            , myDeleterFunc( &deleter::None< Pointee >::func )
        {
            swapWith( *r.p );
        }

        Ownership( std::auto_ptr< Pointee > a )
            : myPtr( a.release() )
            , myDeleterFunc( &deleter::ViaAutoPtr< Pointee >::func )
        {}

        ~Ownership()
        {
            myDeleterFunc( myPtr );
        }

        void swapWith( Ownership& other )
        {
            swap( myPtr, other.myPtr );
            swap( myDeleterFunc, other.myDeleterFunc );
        }

        Pointee* ptr() const            { return myPtr; }
        DeleterFunc deleter() const     { return myDeleterFunc; }

        Pointee* release()
        {
            Pointee* const  result = myPtr;

            myPtr = 0;
            myDeleterFunc = &deleter::None< Pointee >::func;
            return result;
        }
    };

} }  // namespace progrock::cppx

template< class Type >
inline CPPX_IMPLEMENT_SWAP( progrock::cppx::Ownership< Type > )

As you can see this is not a smart pointer in the usual sense. There is no operator* or operator->. Nor is there any implicit pointer conversion. cppx::Ownership does one job only. Namely, ownership.

DISCLAIMER: I’ve been reworking some of this code lately and haven’t tested with g++ yet. And the last time I posted such used-with-one-compiler-only code I was burned. So there’s a chance that this or that needs some adjustment for g++, but it should not be a problem.

Anyway, – enjoy!

Advertisements

3 comments on “[cppx] Ownership with custom deleter.

  1. Bugfix: evidently no-one noticed, but the release() method was incorrect. In the code I posted it used clear(), which caused a premature deletion…. I just removed the clear method and fixed release. It must have been the Keyboard Gremlin™. Or something (this code was OK a week ago).

    And I see that Ownership could benefit from supporting arrays more directly, so I’ll add such support and replace the current code with that.

  2. Pingback: cppx: Xerces strings simplified by ownership, part I. | Alf on programming (mostly C++)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s