This can be done with some indirection ... So I came :)
It is based on the implementation of boost::shared_ptr and can benefit from good acceleration if, instead of holding a pointer to the memory, we actually glue two memory blocks ... but then there are problems with alignment, etc ... so I I wonβt do it from the top of my hat.
First, we need a class whose purpose is to manage our memory, if necessary, if necessary, use a custom deallocator.
It is indirect, and that is where the magic is.
Note that it implements deep copy behavior.
namespace detail { // The interface template <class T> class MemoryOwnerBase { public: virtual ~MemoryOwnerBase() { this->dispose(mItem); mItem = 0; } virtual void dispose(T* item) = 0; virtual void clone() const = 0; T* get() { return mItem; } T* release() { T* tmp = mItem; mItem = 0; return tmp; } void reset(T* item = 0) { if (mItem && item != mItem) this->dispose(mItem); mItem = item; } protected: explicit MemoryOwnerBase(T* i = 0): mItem(i) {} MemoryOwnerBase(const MemoryOwnerBase& rhs): mItem(0) { if (rhs.mItem) mItem = new_clone(*rhs.mItem);
Then we can create our Pimpl class, as it will be yours after.
template <class T> class Pimpl { typedef detail::MemoryOwnerBase<T> owner_base; public: Pimpl(): mItem(0), mOwner(0) {} explicit Pimpl(T* item): mItem(item), mOwner(item == 0 ? 0 : detail::make_owner(item)) {} template <class D> Pimpl(T* item, D d): mItem(item), mOwner(item == 0 ? 0 : detail::make_owner(item, d)) {} Pimpl(const Pimpl& rhs): mItem(), mOwner() { if (rhs.mOwner) { mOwner = rhs.mOwner.clone(); mItem = mOwner->get(); } } T* get() { return mItem; } const T* get() const { return mItem; } void reset(T* item = 0) { if (item && !mOwner) mOwner = detail::make_owner(item); if (mOwner) { mOwner->reset(item); mItem = mOwner->get(); } } template <class D> void reset(T* item, D d) { if (mOwner) { if (mItem == item) mOwner->release(); delete mOwner; } mOwner = detail::make_owner(item, d); mItem = item; } T* operator->() { return mItem; } const T* operator->() const { return mItem; } T& operator*() { return *mItem; } const T& operator*() const { return *mItem; } private: T* mItem;
Alright pfiou!
Now you can use it :)
// myClass.h class MyClass { public: MyClass(); private: struct Impl; Pimpl<Impl> mImpl; }; // myClass.cpp struct MyClass::Impl { Impl(): mA(0), mB(0) {} int mA; int mB; }; // Choice 1 // Easy MyClass::MyClass(): mImpl(new Impl()) {} // Choice 2 // Special purpose allocator (pool ?) struct MyAllocatorDeleter { void dispose(Impl* item) { /* my own routine */ } }; MyClass::MyClass(): mImpl(new Impl(), MyAllocatorDeleter()) {}
Yes, it's magical;)
The principle is to call Type Erasure . The mechanism ensures that as soon as the MemoryOwner object is built, it knows how to delete the stored memory and hide the exact mechanism from the caller through an indirect direction.
Thus, you can consider the Pimpl<T> object as a value:
- Semantics of DeepCopy
- Semantics of DeepConst (
volatile ignored ...) - By default, CopyConstructor, Assignment Operator and Destructor are accurate
But be careful that it hides the pointer, and your role should be honored with its non-null before dereferencing.
The code can be greatly simplified if you remove the lazy initialization of the mOwner parameter. In addition, there are some security restrictions for exceptions: the copy constructor for deletion must be non-throw, otherwise all bets are disabled.
EDIT
Explanations.
The problem here is code isolation. A series of operations can be performed on a pointer regardless of the type that it points to, but to create or destroy we need to know the base type.
The creation and destruction and, therefore, knowledge of the basic type are required in four basic principles:
- Constructor
- Copy constructor
- Assignment operator (destruction of the old value)
- Destructor
which themselves are necessary to obtain the semantics of values.
In C++ there is an idiom called Type Erasure , which consists of information such as embedding behind a virtual interface. And thus, the first part of the design:
template <class T> class MemoryOwnerBase {}; template <class T, class D> class MemoryOwner: public MemoryOwnerBase<T> {};
MemoryOwnerBase provide the basic operations (building, deep copying and destruction) that we are looking for, and hide specific type information (how to delete it correctly).
MemoryOwner implements virtual MemoryOwnerBase methods and encapsulates the knowledge needed to destroy pointers thanks to the D parameter (for deletion).
Now, to manipulate MemoryOwnerBase , we need a pointer / reference to it that has no semantics of values ββand, thus, we transfer it to the Pimpl class (which denotes a pointer to an implementation) that has the correct semantics of values.
Note that only a deletion device is required for deletion (for destruction), since the user must provide the pointer on his own and, therefore, use the new operator.
The refinement will be to provide the Pimpl<T> make_pimpl<T,D>(const T&, const D&) method, which will see the memory allocation, etc .... but I have not received this yet due to the above storage alignment issues .