Click here to Skip to main content
15,881,812 members
Articles / Programming Languages / C++

Another Copy Pointer

Rate me:
Please Sign up or sign in to vote.
4.78/5 (11 votes)
24 Mar 2013MIT6 min read 29.5K   355   34   4
This article describes how to write and use a flexible policy based template copy pointer.

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 TypeLists.

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.

C++
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.

C++
CopyPtr<Dummy> dummyPtr2(dummyPtr);
dummyPtr = dummyPtr2;

In this case, CopyPtr<> uses Dummy's copy constructor but you can use interface copy too.

C++
class TestCopyable : public ICopyable
{
public:
    TestCopyable(){}
        
    virtual ICopyable* copy() const
    {
        // this is my custom copy
        return new TestCopyable(*this);
    }
        
protected:
    TestCopyable(const TestCopyable&){ /* do something */ }
    TestCopyable& operator=(const TestCopyable&) { /* do something */ return *this; }
};

// ...

CopyPtr<ICopyable> testCpy(new TestCopyable);
CopyPtr<ICopyable> testCpy2(testCpy);     // it uses ICopyable::copy() function 
                    // to clone the object

CopyPtr<TestCopyable> testCpy3(new TestCopyable);
CopyPtr<ICopyable> testCpy4(testCpy3); // it still uses the ICopyable::copy() function
testCpy3 = testCpy4; // won't compile, can't convert ICopyable to TestCopyable
testCpy4 = testCpy3; // it works, TestCopyable inherited from ICopyable

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:

C++
struct TestPOD
{
    char c;    
    // and so on...
};
    
template<> struct SupportsBitwiseCopy<TestPOD>
{
    enum { result = true };
};

// ...

CopyPtr<TestPOD> testPod(new TestPOD);
CopyPtr<TestPOD> testPod2(testPod); // using bitwise copy

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.

C++
CopyPtr<Dummy> testDummyCopyable(new Dummy);
CopyPtr<Dummy, CopyHelper::PointerComparison, 
    CopyHelper::NoCopy> testDummyNoCpy(new Dummy);
testDummyCopyable = testDummyNoCopy; // invalid statement, won't compile, 
    // you'll get 'ERROR__Cant_Copy_Object_Ty' has incomplete type message

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.

C++
template <class _Ty>
class CopyConstructorCopy
{
public:
        
    enum {
        isAccessible = 1     // I don't know how to check it 
                // in compile time
    };
        
    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.

C++
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;
        // for bitwise copy, we can call memcpy instead of construct 
        // of the allocator
        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) // not permitted to be null
            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.

C++
template <class _Ty, typename _TInterface = Base::ICopyable>
class InterfaceCopy
{
public:
        
    enum {
        isAccessible = Loki::SuperSubclass< _TInterface, _Ty >::value
    };
        
    static _Ty* copy(const _Ty* pObject)
    {
        // compile time check; _TInterface must be the parent of _Ty
        LOKI_STATIC_CHECK((Loki::SuperSubclass< _TInterface, _Ty >::value), 
            _Ty_Must_Inherit_From_TInterface);

        // if the _Ty::copy() function returns _TInterface*, 
        // we must cast the type back to _Ty
        // we can do it safely with reinterpret_cast, 
        // because we check the right type before in compile time
        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.

C++
template <class _Ty>
class NoCopy
{
public:    

    enum {
        isAccessible = 1
    };
        
    static _Ty* copy(const _Ty* /*pObject*/)
    {
        // in C++11 you can't give a simple false to LOKI_STATIC_CHECK, because it will stop the  
        // compilation with error when you include this file. So it is necessary to add   
        // an expression, because it will be evaluated only when generating the specialized 
        // code from this template.

        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.

C++
// selector.h
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;
};
/**
 * \brief Automatic select the appropriate copy policy.
 */
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:
    
    /**
     * \brief Make a copy from \p other using copy policy.
     */
    static _Ty* copy(const _Ty* other)
    {
        return TCopyPolicy::copy(other);
    }
    
    /**
     * \brief Destroy the object using copy policy.
     */
    static void destroy(_Ty* pObject)
    {
        TCopyPolicy::destroy(pObject);
    }
};

// findpolicy.h
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;
};

/**
* \brief Specialized template. Generates compile time error when there is no accessible copy policy in \p _TCopyPolicies type list.
*/
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:

C++
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;
            
    // using copy policy of other object
    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 

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Employed (other) http://www.otpbank.hu
Hungary Hungary

Comments and Discussions

 
GeneralMy vote of 5 Pin
Loic URIEN21-Mar-13 10:17
Loic URIEN21-Mar-13 10:17 
GeneralRe: My vote of 5 Pin
Gergely Mancz22-Mar-13 1:32
professionalGergely Mancz22-Mar-13 1:32 
GeneralMy vote of 5 Pin
gndnet15-Mar-13 19:02
gndnet15-Mar-13 19:02 
GeneralRe: My vote of 5 Pin
Gergely Mancz15-Mar-13 21:52
professionalGergely Mancz15-Mar-13 21:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.