Designofcopyableandmovabletypes
Design of copyable and movable types

This paper demonstrates:

  • reason to avoid implicitly copyable and movable types, and prefer to make them explicitly copyable;
  • a protocol for explicitly stating in code, where a copy is to be made (boost::copy()), which compliments the protocol for explicitly stating in code, where a move is allowed (std::move() or boost::move());
  • a Boost.move extension for implementing movable types with use of the pass-by-value and swap idiom, backwards-compatible with C++98, which allows to choose copying policies (and eases changing between these policies during maintenance):
    • movable but not copyable,
    • movable and explicitly-copyable,
    • movable and copyable;
  • an example, showing how simply a CloneablePtr can thereby be implemented.

Introduction

Frist, a class CopyMoveDemo is introduced which will help demonstrate how and when various copies and moves occur.

struct CopyMoveDemo
{
BOOST_MOVABLE2(CopyMoveDemo)
public:
CopyMoveDemo() { cout << "CopyMoveDemo()" << endl; }
explicit CopyMoveDemo( int ) { cout << "CopyMoveDemo(int)" << endl; }
CopyMoveDemo( char const* ) { cout << "CopyMoveDemo(str)" << endl; }
CopyMoveDemo( CopyMoveDemo const& ) { cout << "CopyMoveDemo(const&)" << endl; }
CopyMoveDemo( BOOST_RV_REF(CopyMoveDemo) ) { cout << "CopyMoveDemo(&&)" << endl; }
~CopyMoveDemo() { cout << "~CopyMoveDemo()" << endl; }
friend void swap( CopyMoveDemo& a, CopyMoveDemo& b ) BOOST_NOEXCEPT
{ cout << "swap CopyMoveDemo" << endl; }
};

Notice that this class has all the essential member functions: constructors, the destructor, and swap(). In particular, it has one explicit constructor taking an int, and one implicit constructor taking a char const*. The move constructor takes its argument by BOOST_RV_REF(CopyMoveDemo) which is a macro from Boost.move and expands to CopyMoveDemo&& in C++11. All the member functions print their shortened names to cout. The macro call BOOST_MOVABLE2(CopyMoveDemo) adds CopyMoveDemo& operator=( CopyMoveDemo ), implemented in terms of pass-by-value and swap [sutt04]#55. This macro isn't part of Boost. It is introduced in this article as an extention to Boost.move. It doesn't make the object movable, but enables move emulation in C++98 mode. In C++11 the macro expands to:

public:
CopyMoveDemo& operator=( CopyMoveDemo b ) noexcept { swap(*this,b); return *this; }

In C++98, in addition to operator= like above, it enables move emulation by injecting necessary conversion operators, designed by authors of Boost.move.

Note
You may have noticed that operator= is noexcept. It does not mean that an expression like a = b doesn't throw. On the contrary, the copy constructor invoked here can throw. But this expression is strongly exception safe: either the assignment succeeds, or a remains unchanged.

Now, to see how it works:

int main()
{
CopyMoveDemo x;
CopyMoveDemo w1(1);
CopyMoveDemo w2 = "";
CopyMoveDemo w3 = x;
CopyMoveDemo w4 = boost::move(x);
cout << "###" << endl;
w1 = x;
w1 = CopyMoveDemo(1);
w1 = "";
w1 = boost::move(x);
cout << "###" << endl;
}

The output of the above program is the same both in C++98 and C++11, and shows how this class works:

CopyMoveDemo()
CopyMoveDemo(int)
CopyMoveDemo(str)
CopyMoveDemo(const&)
CopyMoveDemo(&&)
###
CopyMoveDemo(const&)
swap CopyMoveDemo
~CopyMoveDemo()
CopyMoveDemo(int)
swap CopyMoveDemo
~CopyMoveDemo()
CopyMoveDemo(str)
swap CopyMoveDemo
~CopyMoveDemo()
CopyMoveDemo(&&)
swap CopyMoveDemo
~CopyMoveDemo()
###
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()

The first section of the output demonstrates calls to five different constructors of clsss CopyMoveDemo. In the middle section of the output you can see how each of the four assignments results in a call to an apropriate constructor followed by a swap operation and a destructor call. Finally, in the third section of the output you can see the effect of the five objects being destroyed.

Now, if instead of using the macro BOOST_MOVABLE2(), we wanted to use `BOOST_COPYABLE_AND_MOVABLE()`, we would have to implement operator= in both copy- and move- versions. And since we were going to write a swap() noexcept anyway [sutt04]#56, BOOST_MOVABLE2() gives us two exception safe operator=s for free.

However, sometimes the pass-by-value and swap version of operator=s may not be the prefered solution. Here are situations, when you may want to avoid the pass-by-value and swap version:

  • when you can't provide a fast swap() noexcept; or
  • when you want operator= to preserve a part of the state of the assigned-to object (like the capacity of a vector; for more about this see [abra09]); or
  • when you can outperform pass-by-value and swap by writing the assignment's overloads by hand (don't forget to measure, if the performance gain matters).

You should choose the solution that suits you best. I recommend the pass-by-value and swap idiom by default, because it is simple, safe, and in certain situations might even be more efficient [sutt04]#27.

Copyable and movable

We'll start examining the subject by designing a class Matrix:

class Matrix
{
// std::vector<double> data_;
CopyMoveDemo data_;
};

This class contains a CopyMoveDemo instead of something like a vector<double>, so that we can observe when copies and moves occur. If we relay only on the compiler generated: constructors, assignment opearators and the destructor, here's what we get:

int main()
{
Matrix const a;
Matrix b = a;
Matrix c = boost::move(a);
cout << "###" << endl;
b = a;
c = boost::move(b);
cout << "###" << endl;
}
C++11 C++98
CopyMoveDemo() CopyMoveDemo()
CopyMoveDemo(const&) CopyMoveDemo(const&)
CopyMoveDemo(&&) CopyMoveDemo(const&)
### ###
CopyMoveDemo(const&) CopyMoveDemo(const&)
swap CopyMoveDemo swap CopyMoveDemo
~CopyMoveDemo() ~CopyMoveDemo()
CopyMoveDemo(&&) CopyMoveDemo(const&)
swap CopyMoveDemo swap CopyMoveDemo
~CopyMoveDemo() ~CopyMoveDemo()
### ###
~CopyMoveDemo() ~CopyMoveDemo()
~CopyMoveDemo() ~CopyMoveDemo()
~CopyMoveDemo() ~CopyMoveDemo()

As you can see, the C++11 version is move-aware, while the C++98 version isn't, but everything compiles and runs correctly in both cases.

Note
If CopyMoveDemo was implemented with the approach described in Boost.move instead, the C++98 version wouldn't compile. The reason is that the copy-assignment operator is autogenerated to take a non-const reference because of how the move emulation is implemented. This is just another reason to prefer the pass-by-value and swap idiom for implementing assignment operators, which we did in class CopyMoveDemo.

We need to add move emulation to class Matrix. We'll use the macro BOOST_MOVABLE2() and add the needed members which are:

  • the move constructor which doesn't get autogenerated in C++98;
  • the default constructor which doesn't get autogenerated once we provide any other constructor;
  • the copy constructor which gets deleted in C++11 when we provide a move constructor;
  • the swap() function for the pass-by-value and swap idiom.

With this version of class Matrix the C++98 output becomes the same as in C++11:

class Matrix
{
// std::vector<double> data_;
CopyMoveDemo data_;
public:
Matrix() {} // = default;
Matrix( Matrix const& b ) : data_(b.data_) {} // = default;
Matrix( BOOST_RV_REF(Matrix) b ) : data_( boost::move(b.data_) ) {} // = default;
friend void swap( Matrix& a, Matrix& b ) BOOST_NOEXCEPT { swap( a.data_, b.data_ ); }
};

There's nothing useful in our Matrix so far, so let's make Matrix addable. As recommended in [sutt04]#27, the operator+= shall do all the work (in our example it will just print "+="), and operator+ will just use +=. Notice, how the first argument of operator+ is passed by value.

class Matrix
{
// std::vector<double> data_;
CopyMoveDemo data_;
public:
Matrix() {} // = default;
Matrix( Matrix const& b ) : data_(b.data_) {} // = default;
Matrix( BOOST_RV_REF(Matrix) b ) : data_( boost::move(b.data_) ) {} // = default;
friend void swap( Matrix& a, Matrix& b ) BOOST_NOEXCEPT { swap( a.data_, b.data_ ); }
Matrix& operator+=( Matrix const& b ) { cout << "+=" << endl; return *this; }
};
inline Matrix operator+( Matrix a, Matrix const& b ) { a += b; return BOOST_MOVE_LOCAL_RETVAL(a); }
Note
The call to BOOST_MOVE_RETURN_LOCAL(a) in the implementation of operator+ expands to return a in C++11 and 'return ::boost::move(a)in C++98. That is because it is recommended not to usemove()when returning a local variable, while themove()` is required with C++98 move emulation.

Let's test the addability of Matrix:

int main()
{
Matrix a, b, c;
cout << "#1#" << endl;
Matrix d = b+c;
cout << "#2#" << endl;
Matrix e = b+c+d;
cout << "#3#" << endl;
Matrix f = b+(c+d);
cout << "###" << endl;
}

The output is the same in C++98 and C++11:

CopyMoveDemo()
CopyMoveDemo()
CopyMoveDemo()
#1#
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
~CopyMoveDemo()
#2#
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
+=
CopyMoveDemo(&&)
~CopyMoveDemo()
~CopyMoveDemo()
#3#
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
###
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()

As you can see, Matrix is now addable, but:

  • invoking + results in at least one copy of a Matrix which is eventually moved from;
  • the temporary copy is created implicitly, so we can't be sure how many temporary copies are actually created without careful analysis of the declaration(s) of operator+;
  • in case #3# two temporary copies are actually created; we could avoid one of them by reordering the operations or adding some overloads of operator+, but the compiler doesn't even warn about the additional copy.

I think the problem here is not that the copies are made. The problem is that the copies are made silently. Just like silent move in std::auto_ptr is considered evil, I believe silent copies are a bad, as they are a potential source of pessimization.

Note
The extra copy in case #3# can be avoided in C++11 by providing four overloads of operator+ (yes, you need all four overloads so that all use cases are unabiguous), but that doesn't fix the root of the problem, which I believe is that the copies are silent.
inline Matrix operator+( Matrix&& a, Matrix const& b ) { a += b; return boost::move(a); }
inline Matrix operator+( Matrix&& a, Matrix&& b ) { a += b; return boost::move(a); }
inline Matrix operator+( Matrix const&b, Matrix&& a ) { a += b; return boost::move(a); }
inline Matrix operator+( Matrix const& a, Matrix const& b ) { return Matrix(a) + b; }
In C++98 it's even harder to supply proper overloads. I won't even try. Let's forget about these four overloads for now; the following examples are based on the original one operator+:
inline Matrix operator+( Matrix a, Matrix const& b )
{ a += b; return BOOST_MOVE_LOCAL_RETVAL(a); }

The conclusion is, the copy constructor should be explicit.

Explicitly copyable

In C++11 making the copy constructor explicit is exactly what we can do:

explicit Matrix( Matrix const& ) = default;

However, in C++98 making the copy constructor explicit would ruine the move emulation and Matrix would become silently movable (like std::auto_ptr) which is unacceptable. But we can make Matrix noncopyable, and then provide some other way (not via the copy constructor) to make a copy of a Matrix.

Consider, how usage of Matrix would change, if it was noncopyable. All we need to change in class Matrix to make it noncopyable is:

And now compiling the last main() function results in expected compile-time errors, as comments indicate below:

int main()
{
Matrix a, b, c;
cout << "#1#" << endl;
Matrix d = b+c; // error: use of deleted copy constructor
cout << "#2#" << endl;
Matrix e = b+c+d; // error: use of deleted copy constructor
cout << "#3#" << endl;
Matrix f = b+(c+d); // error: use of deleted copy constructor
cout << "###" << endl;
}

—TODO Matrix is noncopyable so far, so we can't make copies, but we can still get this to compile by indicating that certain objects may be moved-from. Not all need to be moveable: only the first argument to operator+. Also in #2# it's enough to mark the first argument with boost::move(), because the result of the first call to + is a temporary which is ready to be moved by definition. To avoid the second boost::move() in #3# we can reorder the operations (after all, + is commutative; we could also just remove the parentheses, because + is also associative).

int main()
{
Matrix a, b, c;
cout << "#1#" << endl;
Matrix d = boost::move(b) + c;
cout << "#2#" << endl;
Matrix e = boost::move(b) + c + d;
cout << "#3#" << endl;
Matrix f = ( boost::move(c) + d ) + b;
cout << "###" << endl;
}

Introducing boost::copy()

Whenever one wants to allow a move of a non-temporary object, one uses boost::move() (or std::move()) just like in the main function above. And so a similar function for explicitly allowing a copy is proposed: boost::copy(). It is to be used like boost::move(), whenever one wants to make a copy of a non-temporary object, so that the copy can be moved from, and the original remains unmodified.

So suppose we don't want to allow any moving in the last example and we want copies instead. Here's what we need to say: just replace boost::move() with boost::copy().

int main()
{
Matrix a, b, c;
cout << "#1#" << endl;
Matrix d = boost::copy(b) + c;
cout << "#2#" << endl;
Matrix e = boost::copy(b) + c + d;
cout << "#3#" << endl;
Matrix f = ( boost::copy(c) + d ) + b;
cout << "###" << endl;
}

For this to work we also need to change one line in class Matrix:

That's it. Now the above sample compiles and works both in C++11 and C++98, making only the three explicitly requested copies. The following output is from running the C++11 version, while in C++98 more temporary objects are created, but they are only used for moving the existing copies of Matrix around, and no additional copies are introduced. That is because in C++11 some temporaries can be optimized away by move-ellision, while it's not allowed with move emulation in C++98.

CopyMoveDemo()
CopyMoveDemo()
CopyMoveDemo()
#1#
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
~CopyMoveDemo()
#2#
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
+=
CopyMoveDemo(&&)
~CopyMoveDemo()
~CopyMoveDemo()
#3#
CopyMoveDemo(const&)
+=
CopyMoveDemo(&&)
+=
CopyMoveDemo(&&)
~CopyMoveDemo()
~CopyMoveDemo()
###
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()
~CopyMoveDemo()

Boost.move extension

The tools presented in this paper are part of an extension to Boost.move. That is, they use Boost.move and operate with it. They are:

Note
For more information about these tools check out their documentation.

Customizing boost::copy()

You can customize boost::copy() for any type, even if it is not copyable at all, and make it explicitly copyable. You only need to overload the function copy_adl() for your type in your type's namespace, and boost::copy() will find it via the ADL. For example, it's easy to create a CloneablePtr out of unique_ptr with a custom deleter – behold:

  • a custom deleter, based on boost::heap_clone_allocator from Boost.ptr_container:
    template < class T, class CloneAllocator = boost::heap_clone_allocator >
    struct CloneableDelete : CloneAllocator
    {
    typedef CloneAllocator clone_allocator;
    CloneableDelete() {}
    template < class U >
    CloneableDelete( CloneableDelete<U,CloneAllocator> const&,
    typename std::enable_if< std::is_convertible<U*,T*>::value >::type* = 0 )
    {}
    void operator()( T* ptr ) const
    { this->deallocate_clone(ptr); }
    };
  • an alias and a make_* function to make it a user-friendly smart pointer:
    template < class T, class CloneAllocator = boost::heap_clone_allocator >
    using CloneablePtr = std::unique_ptr< T, CloneableDelete<T,CloneAllocator> >;
    template < class T, class... Args >
    inline
    CloneablePtr<T> make_Cloneable( Args&&... args )
    { return CloneablePtr<T>{ new T( std::forward<Args>(args)... ) }; }
  • finally, to make it explicitly-copyable – an overload of copy_adl():
    template < class T, class CloneAllocator >
    CloneablePtr<T,CloneAllocator> copy_adl( CloneablePtr<T,CloneAllocator> const& x )
    {
    typedef CloneablePtr<T,CloneAllocator> Ptr;
    return
    x ?
    Ptr( x.get_deleter().allocate_clone(*x), x.get_deleter() )
    :
    Ptr( 0, x.get_deleter() );
    ;
    }
  • and usage with the program output given in comments on the right:
    struct A
    {
    A() { std::cout << "A()" << std::endl; }
    A( A const& ) { std::cout << "A(c&)" << std::endl; }
    virtual ~A() { std::cout << "~A()" << std::endl; }
    virtual A* clone() const { return new A(*this); }
    };
    // used via ADL by boost::heap_clone_allocator
    A* new_clone( A const& a )
    { return a.clone(); }
    struct B : A
    {
    B() { std::cout << "B()" << std::endl; }
    B( B const& b ) : A(b) { std::cout << "B(c&)" << std::endl; }
    ~B() { std::cout << "~B()" << std::endl; }
    B* clone() const { return new B(*this); }
    };
    int main()
    {
    CloneablePtr<A> a0( new A ); // A()
    std::cout << "-- a0 contains an A" << std::endl;
    // -- a0 contains an A
    a0 = make_Cloneable<A>(); // A()
    // ~A();
    std::cout << "-- a0 contains an other A" << std::endl;
    // -- a0 contains an other A
    auto a1 = boost::copy(a0); // A(c&)
    std::cout << "-- a1 contains a copy of a0's A" << std::endl;
    // -- a1 contains a copy of a0's A
    a0 = make_Cloneable<B>(); // A()
    // B()
    // ~A()
    std::cout << "-- a0 contains a B" << std::endl;
    // -- a0 contains a B
    auto a2 = boost::copy(a0); // A(c&)
    // B(c&)
    std::cout << "-- a2 contains a copy of a0's B" << std::endl;
    // -- a2 contains a copy of a0's B
    // ~B()
    // ~A()
    // ~A()
    // ~B()
    // ~A()
    }

Simple and powerful.

Conclusion

In conclusion, in this paper the pass-by-value and swap idiom for implementing assignment operators is recommended, as well as using an extension to Boost.move which uses this idiom. Copyable and movable types are recommended to be explicitly copyable and boost::copy() is proposed as the protocol for explicitly requesting a copy in code. By customizing boost::copy(), any not necessarily copyable type can be made explicitly copyable via boost::copy().

References

[Boost.move]

The Boost.move library

By Ion Gaztanaga

[Boost.ptr_container]

The Boost.ptr_container library

By Thorsten Ottosen

[sutt04]

C++ Coding Standards: 101 Rules, Guidelines, and Best Practices

By Herb Sutter, Andrei Alexandrescu

[abra09]

Your Next Assignment...

Posted September 28, 2009 by Dave Abrahams, under Value Semantics