Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Implementing copyable and movable classes

Copyable and movable classes in C++0x
Copyable and movable classes in portable syntax for both C++03 and C++0x compilers

Consider a simple handle class that owns a resource and also provides copy semantics (copy constructor and assignment). For example a clone_ptr might own a pointer, and call clone() on it for copying purposes:

template <class T>
class clone_ptr
{
   private:
   T* ptr;

   public:
   // construction
   explicit clone_ptr(T* p = 0) : ptr(p) {}

   // destruction
   ~clone_ptr() { delete ptr; }

   // copy semantics
   clone_ptr(const clone_ptr& p)
      : ptr(p.ptr ? p.ptr->clone() : 0) {}

   clone_ptr& operator=(const clone_ptr& p)
   {
      if (this != &p)
      {
         T *p = p.ptr ? p.ptr->clone() : 0;
         delete ptr;
         ptr = p;
      }
      return *this;
   }

   // move semantics
   clone_ptr(clone_ptr&& p)
      : ptr(p.ptr) { p.ptr = 0; }

   clone_ptr& operator=(clone_ptr&& p)
   {
      std::swap(ptr, p.ptr);
      delete p.ptr;
      p.ptr = 0;
      return *this;
   }

   // Other operations...
};

clone_ptr has expected copy constructor and assignment semantics, duplicating resources when copying. Note that copy constructing or assigning a clone_ptr is a relatively expensive operation:

clone_ptr<Base> p1(new Derived());
// ...
clone_ptr<Base> p2 = p1;  // p2 and p1 each own their own pointer

clone_ptr is code that you might find in today's books on C++, except for the part marked as move semantics. That part is implemented in terms of C++0x rvalue references. You can find some good introduction and tutorials on rvalue references in these papers:

When the source of the copy is known to be an rvalue (e.g.: a temporary object), one can avoid the potentially expensive clone() operation by pilfering source's pointer (no one will notice!). The move constructor above does exactly that, leaving the rvalue in a default constructed state. The move assignment operator simply does the same freeing old resources.

Now when code tries to copy an rvalue clone_ptr, or if that code explicitly gives permission to consider the source of the copy an rvalue (using boost::move), the operation will execute much faster.

clone_ptr<Base> p1(new Derived());
// ...
clone_ptr<Base> p2 = boost::move(p1);  // p2 now owns the pointer instead of p1
p2 = clone_ptr<Base>(new Derived());   // temporary is moved to p2
}

Many aspects of move semantics can be emulated for compilers not supporting rvalue references and Boost.Move offers tools for that purpose. With Boost.Move we can write clone_ptr so that it will work both in compilers with rvalue references and those who conform to C++03. You just need to follow these simple steps:

Let's see how are applied to clone_ptr:

template <class T>
class clone_ptr
{
   private:
   // Mark this class copyable and movable
   BOOST_COPYABLE_AND_MOVABLE(clone_ptr)
   T* ptr;

   public:
   // Construction
   explicit clone_ptr(T* p = 0) : ptr(p) {}

   // Destruction
   ~clone_ptr() { delete ptr; }

   clone_ptr(const clone_ptr& p) // Copy constructor (as usual)
      : ptr(p.ptr ? p.ptr->clone() : 0) {}

   clone_ptr& operator=(BOOST_COPY_ASSIGN_REF(clone_ptr) p) // Copy assignment
   {
      if (this != &p){
         T *tmp_p = p.ptr ? p.ptr->clone() : 0;
         delete ptr;
         ptr = tmp_p;
      }
      return *this;
   }

   //Move semantics...
   clone_ptr(BOOST_RV_REF(clone_ptr) p)            //Move constructor
      : ptr(p.ptr) { p.ptr = 0; }

   clone_ptr& operator=(BOOST_RV_REF(clone_ptr) p) //Move assignment
   {
      if (this != &p){
         delete ptr;
         ptr = p.ptr;
         p.ptr = 0;
      }
      return *this;
   }
};

Question: What about types that don't own resources? (E.g. std::complex?)

No work needs to be done in that case. The copy constructor is already optimal.


PrevUpHomeNext