Home | Libraries | People | FAQ | More |
Like any emulation effort, the library has some limitations users should take in care to achieve portable and efficient code when using the library with C++03 conformant compilers:
When initializing base classes in move constructors, users must cast the
reference to a base class reference before moving it or just use BOOST_MOVE_BASE
. Example:
Derived(BOOST_RV_REF(Derived) x) // Move ctor : Base(boost::move(static_cast<Base&>(x))) //...
or
Derived(BOOST_RV_REF(Derived) x) // Move ctor : Base(BOOST_MOVE_BASE(Base, x)) //...
If casting is not performed the emulation will not move construct the base
class, because no conversion is available from BOOST_RV_REF(Derived)
to BOOST_RV_REF(Base)
.
Without the cast or BOOST_MOVE_BASE
we might obtain a compilation error (for non-copyable types) or a less-efficient
move constructor (for copyable types):
//If Derived is copyable, then Base is copy-constructed. //If not, a compilation error is issued Derived(BOOST_RV_REF(Derived) x) // Move ctor : Base(boost::move(x)) //...
The emulation can't deal with C++0x reference collapsing rules that allow perfect forwarding:
//C++0x template<class T> void forward_function(T &&t) { inner_function(std::forward<T>(t); } //Wrong C++03 emulation template<class T> void forward_function(BOOST_RV_REF<T> t) { inner_function(boost::forward<T>(t); }
In C++03 emulation BOOST_RV_REF doesn't catch any const rlvalues. For more details on forwarding see Constructor Forwarding chapter.
The first rvalue reference proposal allowed the binding of rvalue references to lvalues:
func(Type &&t); //.... Type t; //Allowed func(t)
Later, as explained in Fixing a Safety Problem with Rvalue References this behaviour was considered dangerous and eliminated this binding so that rvalue references adhere to the principle of type-safe overloading: Every function must be type-safe in isolation, without regard to how it has been overloaded
Boost.Move can't emulate this type-safe overloading principle for C++03 compilers:
//Allowed by move emulation movable m; BOOST_RV_REF(movable) r = m;
The macro BOOST_COPYABLE_AND_MOVABLE
needs to define a copy constructor for copyable_and_movable
taking a non-const parameter in C++03 compilers:
//Generated by BOOST_COPYABLE_AND_MOVABLE copyable_and_movable &operator=(copyable_and_movable&){/**/}
Since the non-const overload of the copy constructor is generated, compiler-generated
assignment operators for classes containing copyable_and_movable
will get the non-const copy constructor overload, which will surely surprise
users:
class holder { copyable_and_movable c; }; void func(const holder& h) { holder copy_h(h); //<--- ERROR: can't convert 'const holder&' to 'holder&' //Compiler-generated copy constructor is non-const: // holder& operator(holder &) //!!! }
This limitation forces the user to define a const version of the copy assignment, in all classes holding copyable and movable classes which might be annoying in some cases.
An alternative is to implement a single operator
=()
for copyable and movable classes
using
"pass by value" semantics:
T& operator=(T x) // x is a copy of the source; hard work already done { swap(*this, x); // trade our resources for x's return *this; // our (old) resources get destroyed with x }
However, "pass by value" is not optimal for classes (like containers, strings, etc.) that reuse resources (like previously allocated memory) when x is assigned from a lvalue.
Given a movable and copyable class, if a templated assignment operator (*) is added:
class Foo { BOOST_COPYABLE_AND_MOVABLE(Foo) public: int i; explicit Foo(int val) : i(val) {} Foo(BOOST_RV_REF(Foo) obj) : i(obj.i) {} Foo& operator=(BOOST_RV_REF(Foo) rhs) { i = rhs.i; rhs.i = 0; return *this; } Foo& operator=(BOOST_COPY_ASSIGN_REF(Foo) rhs) { i = rhs.i; return *this; } //(1) template<class U> //(*) TEMPLATED ASSIGNMENT, potential problem Foo& operator=(const U& rhs) { i = -rhs.i; return *this; } //(2) };
C++98 and C++11 compilers will behave different when assigning from a [const]
Foo
lvalue:
Foo foo1(1); Foo foo2(2); foo2 = foo1; // Calls (1) in C++11 but (2) in C++98 const Foo foo5(5); foo2 = foo5; // Calls (1) in C++11 but (2) in C++98
This different behaviour is a side-effect of the move emulation that can't
be easily avoided by Boost.Move. One workaround
is to SFINAE-out the templated assignment operator with disable_if
:
template<class U> // Modified templated assignment typename boost::disable_if<boost::is_same<U, Foo>, Foo&>::type operator=(const U& rhs) { i = -rhs.i; return *this; } //(2)