Introduction
I have decided to write a copy pointer class which is a smart pointer and can hold a pointer and make a deep copy of the object.
There are many implementations of a copy pointer class on the Internet but they aren't flexible enough to me. The standard approach I've found is to copy the object via its copy constructor using the new T(*pointer_to_T)
form. This solution doesn't work if we want to use interface pointers. My goal was to make an easy to use, flexible, and efficient copy pointer class which can cover most of the cases.
In this solution, I used the Loki library which provides many useful classes like SuperSubclass
and TypeList
s.
Now, let's start with some sample code to learn what can be done with this CopyPtr<>
class.
Using the Code
The basic concept is to use the object like an std::auto_ptr
.
class Dummy{};
CopyPtr<Dummy> dummyPtr(new Dummy);
When the dummyPtr
leaves its scope, the destructor will destroy it.
To copy them between each other, simply use the equal operator or copy constructor.
CopyPtr<Dummy> dummyPtr2(dummyPtr);
dummyPtr = dummyPtr2;
In this case, CopyPtr<>
uses Dummy
's copy constructor but you can use interface copy too.
class TestCopyable : public ICopyable
{
public:
TestCopyable(){}
virtual ICopyable* copy() const
{
return new TestCopyable(*this);
}
protected:
TestCopyable(const TestCopyable&){ }
TestCopyable& operator=(const TestCopyable&) { return *this; }
};
CopyPtr<ICopyable> testCpy(new TestCopyable);
CopyPtr<ICopyable> testCpy2(testCpy);
CopyPtr<TestCopyable> testCpy3(new TestCopyable);
CopyPtr<ICopyable> testCpy4(testCpy3); testCpy3 = testCpy4; testCpy4 = testCpy3;
Sometimes you want to copy POD (Plain Old Data) types and in this case, you could use bitwise copy. We have the choice to use it if we want; just do something like this:
struct TestPOD
{
char c;
};
template<> struct SupportsBitwiseCopy<TestPOD>
{
enum { result = true };
};
CopyPtr<TestPOD> testPod(new TestPOD);
CopyPtr<TestPOD> testPod2(testPod);
If you specialize SupportsBitwiseCopy
with your type, every copy on your POD will be bitwise copied.
There is another strategy to use, which is a cuckoo's egg; it doesn't make copies, because it prevents the copy of the object.
CopyPtr<Dummy> testDummyCopyable(new Dummy);
CopyPtr<Dummy, CopyHelper::PointerComparison,
CopyHelper::NoCopy> testDummyNoCpy(new Dummy);
testDummyCopyable = testDummyNoCopy;
OK, now you can ask, why use the bitwise copy in one of the previous examples, and why not the copy constructor?
What is the order of strategies between copies? How can the order of these strategies be determined?
In brief, you can determine new strategies and you can determine their priorities. Let's see that kind of magic behind the scenes!
The Techniques
I used two techniques: policy-based template programming and template meta-programming (TMP). The benefits of policy-based programming is described well by Andrei Alexandrescu in Modern C++ Design:
"Policy-based class design fosters assembling a class with complex behavior out of many little classes (called policies), each of which takes care of only one behavioral or structural aspect. As the name suggests, a policy establishes an interface pertaining to a specific issue. You can implement policies in various ways as long as you respect the policy interface. Because you can mix and match policies, you can achieve a combinatorial set of behaviors by using a small core of elementary components."
Template policies give us the biggest flexibility and the most efficient code, and we can fine tune our classes to earn the expected result. I used this technique in the CopyPtr<>
, Selector<>
, InterfaceCopy<>
, and BitwiseCopy<>
classes. The other technique is template meta-programming; here is a description from Wiki:
"Template metaprogramming is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled. The output of these templates include compile-time constants, data structures, and complete functions. The use of templates can be thought of as compile-time execution."
I used a simple template meta-program in FindPolicy<>
.
Anatomy of Parts
The main part is the CopyPtr<>
class which holds the strategy together. It accepts two strategies (_TComparisonPolicy
, _TCopyPolicy
), which grants the flexibility. The comparison policy is accountable to do the wanted comparison between two CopyPtr<>
s. You can select between two strategies: PointerComparison<>
and ValueComparison<>
. By default, CopyPtr<>
uses PointerComparison<>
and ValueComparison<>
which compare the template type by value (using the operator==()
function). There are two kinds of copy policies: one of them just does its copy strategy, and the other can choose the right copy policy at compile time. Here they are:
CopyConstructorCopy<>
This is the simplest strategy; it just calls the copy constructor of the type. Sadly, I can't determine that the copy constructor is to be accessible or not. I didn't find any function which can determine the visibility of the copy constructor without a compile error.
template <class _Ty>
class CopyConstructorCopy
{
public:
enum {
isAccessible = 1 };
static _Ty* copy(const _Ty* pObject)
{
return new _Ty(*pObject);
}
static void destroy(_Ty* pObject)
{
delete pObject, pObject = 0;
}
private:
CopyConstructorCopy();
CopyConstructorCopy(const CopyConstructorCopy&);
CopyConstructorCopy& operator=(const CopyConstructorCopy&);
};
BitwiseCopy<>
Copies the object from bit to bit, using memcpy()
and std::allocator
to allocate memory. Of course, the allocator is a template parameter, so you can use your own allocator.
template <class _Ty, template <class _Ty> class _TAllocator = std::allocator>
class BitwiseCopy
{
public:
enum {
isAccessible = SupportsBitwiseCopy<_Ty>::result
};
static _Ty* copy(const _Ty* pObject)
{
LOKI_STATIC_CHECK(!!SupportsBitwiseCopy<_Ty>::result, _BitwiseCopy_Not_Allowed_For_This_Type);
_TAllocator<_Ty> tmpAllocator;
_Ty* pTy = tmpAllocator.allocate(1);
if (!pTy)
return 0;
memcpy(pTy, pObject, sizeof(_Ty));
return pTy;
}
static void destroy(_Ty* pObject)
{
LOKI_STATIC_CHECK(!!SupportsBitwiseCopy<_Ty>::result, _BitwiseCopy_Not_Allowed_For_This_Type);
if (!pObject) return;
_TAllocator<_Ty> tmpAllocator;
tmpAllocator.deallocate(pObject, 1);
}
private:
BitwiseCopy();
BitwiseCopy(const BitwiseCopy&);
BitwiseCopy& operator=(const BitwiseCopy&);
};
InterfaceCopy<>
It uses my ICopyable
interface by default (also a template parameter) and it calls a copy()
function and it checks the inheritance of the object at compile time, so if you try to copy the object but it isn't the child of InterfaceCopy<>
, you'll get a compile error.
template <class _Ty, typename _TInterface = Base::ICopyable>
class InterfaceCopy
{
public:
enum {
isAccessible = Loki::SuperSubclass< _TInterface, _Ty >::value
};
static _Ty* copy(const _Ty* pObject)
{
LOKI_STATIC_CHECK((Loki::SuperSubclass< _TInterface, _Ty >::value),
_Ty_Must_Inherit_From_TInterface);
return reinterpret_cast<_Ty*>(pObject->copy());
}
static void destroy(_Ty* pObject)
{
delete pObject, pObject = 0;
}
private:
InterfaceCopy();
InterfaceCopy(const InterfaceCopy&);
InterfaceCopy& operator=(const InterfaceCopy&);
};
NoCopy<>
The cuckoo's egg. You will get a compile error when you try to copy the object. You can use it, when you want to prevent to copy the object.
template <class _Ty>
class NoCopy
{
public:
enum {
isAccessible = 1
};
static _Ty* copy(const _Ty* )
{
LOKI_STATIC_CHECK(0 == isAccessible, _Cant_Copy_Object_Ty);
return 0;
}
static void destroy(_Ty* pObject)
{
delete pObject, pObject = 0;
}
private:
NoCopy();
NoCopy(const NoCopy&);
NoCopy& operator=(const NoCopy&);
};
These were the simple strategies of _TCopyPolicy
.
Selector<>
The Selector<>
class helps you select the right strategy for your classes. You can prioritize the right order of the strategies and it will do the whole work well. The selector is built up of two classes: Selector<>
is a wrapper class which provides the same interface as the other copy policies, and the FindPolicy<>
class which finds the right policy to use. It iterates through the policies and checks their isAccessible
enum. If this enum is 1, we find the right policy, a specialized template will be instantiated, and the iteration will stop. As a matter of fact, the iteration is a recursion solved by template meta-programming.
template <class _Ty>
class DefaultCopyPolicy
{
public:
typedef typename Loki::TL::MakeTypelist<
InterfaceCopy<_Ty>,
BitwiseCopy<_Ty>,
CopyConstructorCopy<_Ty>,
NoCopy<_Ty>
>::Result Result;
};
template <class _Ty, class _TCopyPolicy> class Selector;
template <class _Ty>
class Selector<_Ty, Loki::NullType>
{
public:
class No_Type_Specified_In_Type_List;
No_Type_Specified_In_Type_List BadFood;
};
template <class _Ty, class _TCopyPolicy = typename DefaultCopyPolicy<_Ty>::Result >
class Selector
{
protected:
typedef typename Loki::TL::TypeAt<_TCopyPolicy, 0>::Result TCurrentCopy;
typedef typename FindPolicy<_Ty, _TCopyPolicy, TCurrentCopy,
!!TCurrentCopy::isAccessible >::TCopyPolicy TCopyPolicy;
public:
static _Ty* copy(const _Ty* other)
{
return TCopyPolicy::copy(other);
}
static void destroy(_Ty* pObject)
{
TCopyPolicy::destroy(pObject);
}
};
template <class _Ty, class _TCopyPolicies, class _TCurrentCopy, bool isFound> class FindPolicy;
template <class _Ty, class _TCopyPolicies, class _TCurrentCopy>
class FindPolicy<_Ty, _TCopyPolicies, _TCurrentCopy, true>
{
public:
typedef _TCurrentCopy TCopyPolicy;
};
template <class _Ty, class _TCurrentCopy>
class FindPolicy<_Ty, Loki::NullType, _TCurrentCopy, false>
{
public:
class There_Is_No_Accessible_Copy_Policy;
There_Is_No_Accessible_Copy_Policy No_Copy_Policy_Found;
};
template <class _Ty, class _TCopyPolicies, class _TCurrentCopy>
class FindPolicy<_Ty, _TCopyPolicies, _TCurrentCopy, false>
{
public:
typedef _TCurrentCopy TCurrentCopy;
typedef typename Loki::TL::TypeAt<_TCopyPolicies, 0>::Result TNextCopy;
typedef typename FindPolicy<_Ty, typename Loki::TL::Erase<_TCopyPolicies,
TCurrentCopy>::Result,
TNextCopy, !!TCurrentCopy::isAccessible >::TCopyPolicy TCopyPolicy;
};
The first line (in findpolicy.h) is the declaration of the function. The last template is the "recursive function" which does the job, and the specialized class (in the middle) is the exit point from the recursion. I used Loki TypeList
to reduce the implemented code. It is important to notice everything is happening at compile time, no runtime overhead.
CopyPtr<>
The last part is the CopyPtr<>
class which combines the elements. It has three template parameters: the type which will be handled, the comparison policy, and the strategy for CopyPtr<>
.
The comparison policy isn't so complex, you can decide if the class uses pointer comparison or value comparison when comparing CopyPtr<>
objects. The copy policy can be a simple strategy or a complex strategy (like the Selector<>
). There is nothing special in the class, you can get()
, reset()
, or release()
the contained object, or you can swap()
it if you want. swap()
uses std::swap
by default but you can use your own swap function too. The only thing which is worthy of note is operator=()
. There are two types of operator=()
: one is the standard and the other is a template one:
template <class _Ty, template <typename> class _TComparisonPolicy, class _TCopyPolicy>
template <class _TOther, template <typename> class _TOtherComparisonPolicy,
class _TOtherCopyPolicy>
CopyPtr<_Ty, _TComparisonPolicy, _TCopyPolicy>& CopyPtr<_Ty,
_TComparisonPolicy, _TCopyPolicy>::operator=(const CopyPtr<_TOther,
_TOtherComparisonPolicy, _TOtherCopyPolicy>& other)
{
LOKI_STATIC_CHECK((Loki::SuperSubclass< _Ty, _TOther >::value),
_TOther_Must_Inherit_From_Ty);
reset();
if (other.isNull())
return *this;
m_pObject = _TOtherCopyPolicy::copy(other.get());
return *this;
}
This function is written for an object which is a child of the template type (_Ty
). So it checks the inheritance and besides it copies the object with the other's copy policy. We have to use the other's copy strategy and the new pointer must be compatible with the _Ty
type. The full source code is attached, check it.
History
- 11 May 2011: First version.
- 15 Mar 2013: Added Google test files, small bug fix.
- 24 Mar 2013: Add more test and fix some bug
- NoCopy Policy didn't worked on C++11
- Handle the case, when no copy policy found or wrong copy policy specified
- add tests for compile time checks