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
This is an older version of Boost and was released in 2018. The current version is 1.89.0.
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)