Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Implicit Move when returning a local object

The C++ standard specifies situations where an implicit move operation is safe and the compiler can use it in cases were the (Named) Return Value Optimization) can't be used. The typical use case is when a function returns a named (non-temporary) object by value and the following code will perfectly compile in C++11:

//Even if movable can't be copied
//the compiler will call the move-constructor
//to generate the return value
//
//The compiler can also optimize out the move
//and directly construct the object 'm'
movable factory()
{
   movable tmp;
   m = ...
   //(1) moved instead of copied
   return tmp;
};

//Initialize object
movable m(factory());

In compilers without rvalue references and some non-conforming compilers (such as Visual C++ 2010/2012) the line marked with (1) would trigger a compilation error because movable can't be copied. Using a explicit ::boost::move(tmp) would workaround this limitation but it would code suboptimal in C++11 compilers (as the compile could not use (N)RVO to optimize-away the copy/move).

Boost.Move offers an additional macro called BOOST_MOVE_RET that can be used to alleviate this problem obtaining portable move-on-return semantics. Let's use the previously presented movable-only movable class with classes copyable (copy-only type), copy_movable (can be copied and moved) and non_copy_movable (non-copyable and non-movable):

//header file "copymovable.hpp"
#include <boost/move/core.hpp>

//A copy_movable class
class copy_movable
{
   BOOST_COPYABLE_AND_MOVABLE(copy_movable)
   int value_;

   public:
   copy_movable() : value_(1){}

   //Move constructor and assignment
   copy_movable(BOOST_RV_REF(copy_movable) m)
   {  value_ = m.value_;   m.value_ = 0;  }

   copy_movable(const copy_movable &m)
   {  value_ = m.value_;   }

   copy_movable & operator=(BOOST_RV_REF(copy_movable) m)
   {  value_ = m.value_;   m.value_ = 0;  return *this;  }

   copy_movable & operator=(BOOST_COPY_ASSIGN_REF(copy_movable) m)
   {  value_ = m.value_;   return *this;  }

   bool moved() const //Observer
   {  return value_ == 0; }
};

//A copyable-only class
class copyable
{};

//A copyable-only class
class non_copy_movable
{
   public:
   non_copy_movable(){}
   private:
   non_copy_movable(const non_copy_movable&);
   non_copy_movable& operator=(const non_copy_movable&);
};

and build a generic factory function that returns a newly constructed value or a reference to an already constructed object.

#include "movable.hpp"
#include "copymovable.hpp"
#include <boost/move/core.hpp>

template<class Type>
struct factory_functor
{
   typedef Type return_type;

   Type operator()() const
   {  Type t;  return BOOST_MOVE_RET(Type, t);  }
};

struct return_reference
{
   typedef non_copy_movable &return_type;

   non_copy_movable &operator()() const
   {  return ncm; }

   static non_copy_movable ncm;
};

non_copy_movable return_reference::ncm;

//A wrapper that locks a mutex while the
//factory creates a new value.
//It must generically move the return value
//if possible both in C++03 and C++11
template <class Factory>
typename Factory::return_type lock_wrapper(Factory f)
{
   typedef typename Factory::return_type return_type;
   //LOCK();
   return_type r = f();
   //UNLOCK();

   //In C++03: boost::move() if R is not a reference and
   //has move emulation enabled. In C++11: just return r.
   return BOOST_MOVE_RET(return_type, r);
}

int main()
{
   movable m            = lock_wrapper(factory_functor<movable>     ());
   copy_movable cm      = lock_wrapper(factory_functor<copy_movable>());
   copyable c           = lock_wrapper(factory_functor<copyable>    ());
   non_copy_movable &mr = lock_wrapper(return_reference             ());
   return 0;
}

Caution: When using this macro in a non-conforming or C++03 compilers, a move will be performed even if the C++11 standard does not allow it (e.g. returning a static variable). The user is responsible for using this macro only used to return local objects that met C++11 criteria. E.g.:

struct foo
{
   copy_movable operator()() const
   {
      //ERROR! The Standard does not allow implicit move returns when the object to be returned 
      //does not met the criteria for elision of a copy operation (such as returning a static member data)
      //In C++03 compilers this will MOVE resources from cm
      //In C++11 compilers this will COPY resources from cm
      //so DON'T use use BOOST_MOVE_RET without care.
      return BOOST_MOVE_RET(copy_movable, cm);
   }

   static copy_movable cm;
};

Note: When returning a temporary object BOOST_MOVE_REF is not needed as copy ellision rules will work on both C++03 and C++11 compilers.

//Note: no BOOST_MOVE_RET
movable get_movable()
{  return movable();  }

copy_movable get_copy_movable()
{  return copy_movable();  }

copyable get_copyable()
{  return copyable();  }

PrevUpHomeNext