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, anoperator T&()
, is ignored for an object of typeT
; 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!
Bugfix: evidently no-one noticed, but the
release()
method was incorrect. In the code I posted it usedclear()
, which caused a premature deletion…. I just removed theclear
method and fixedrelease
. 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.Pingback: cppx: Xerces strings simplified by ownership, part I. | Alf on programming (mostly C++)
Hi, Have a look at https://github.com/codenrun/auto_ptr_custom . It provides similar functionality as yours.