.. include:: .. include:: .. _move-semantics: Move Semantics ============== Overloading Constructors and Assignment Operators with rvalue references ------------------------------------------------------------------------ .. todo:: See also https://www.fluentcpp.com/2018/07/17/how-to-construct-c-objects-without-making-copies/ When a copy constructor or an assignment operator is invoked, the source object remains unchanged. But if the source object is about to die and its resources about to be deleted, it obviously is be more efficient to do a shallow copy, to simply transfer the ownership of the source object's resources, like heap memory, to the new object. For example, take the String class below. .. code-block:: cpp class String { private: char *p; int length; public: String() : p{nullptr}, length{0} {} String(char *ptr) { length = strlen(ptr); p = new char[length + 1]; strcpy(p, ptr); } String(const String& str) : length{p.length} { p = new char[length + 1]; strcpy(p, ptr); } String& operator=(const String& str) { if (this != &str) { delete [] p; length = strlen(str.length); p = new char[length + 1]; strcpy(p, str.p); } return *this; } // .... }; String s1{"abc"}; String s2{"def"}; String s3{s1 + s2}; The temporary string representing *s1 + s2* will die after the line is executed and its memory will be deleted. Therefore it is to move to *s1 + s2* to *s3* like this .. code-block:: cpp String::String(String&& str) : length{str.length}, p{str.p} { str.length = 0; str.p = nullptr; } When C++11 introduced rvalue references, it allowed constructors and assignment operators to be overloaed with rvalue references, like the constructor above (called a move constructor). This allows the compiler to branch at compiler time depending on whether the constructor or assignment operator is being passed a temporary value (or has been cast to a temporary value). The ``Vector`` class below was introduced in :ref:`rvalue-reference`. Move semantics allow you to overloaded a class\ |apos|\ s constructor and assignment operator with a new type of reference called an **rvalue reference** Doing so allows the compiler to always choose the more effecient move constructor and move assignment operator when an rvalue is encountered. Below is a template ``Vector`` class with the usual copy constructor and assignment operator as well as ``void push_back(const T&)`` that take an ``const T&``: .. code-block:: cpp #include #include #include #include template class Vector { std::unique_ptr p; int size; int current; void grow(); static const int growth_factor = 2; static const int default_sz = 2; public: Vector() : p(std::make_unique(Vector::default_sz)), size{Vector::default_sz}, current{0} { } Vector(std::initializer_list lst) : p(std::make_unique(Vector::default_sz )), size{Vector::default_sz}, current{0} { for (auto& x : lst) { push_back(std::move(x)); } } Vector(const Vector& lhs); Vector& operator=(const Vector& lhs); void push_back(const T& t); // Does deep copy void push_back(T&& t); // Does shallow copy T& operator[](int); const T& operator[](int) const; std::ostream& print(std::ostream& ostr) const { if (size != 0) { std::copy(p.get(), p.get() + current, std::ostream_iterator(ostr, ", ")); } return ostr; } friend std::ostream& operator<<(std::ostream& ostr, const Vector& vec) { return vec.print(ostr); } int count() const { return size; } void* operator new (std::size_t size, void* ptr) noexcept; }; template inline Vector::Vector(const Vector& lhs) : p{new T[lhs.size]}, size{lhs.size}, current{lhs.current} { std::copy(p.get(), lhs.p, lhs.p + lhs.size); } template Vector& Vector::operator=(const Vector& lhs) { if (this != &lhs) { p = std::make_unique(new T[lhs.size]); size = lhs.size; copy(p, lhs.p, lhs.p + lhs.size); } return *this; } template void Vector::grow() { auto new_size = size * Vector::growth_factor; std::unique_ptr ptr = std::make_unique(new_size); for (auto i = 0; i < size; ++i) { ptr[i] = std::move(p[i]); } size = new_size; p = std::move(ptr); } template void Vector::push_back(const T& t) { if (current == size) { grow(); } p[current++] = t; } template T& Vector::operator[](int pos) { if (pos >= size || pos < 0) { throw(std::out_of_range("pos not in range.")); } else { return p[pos]; } } template inline const T& Vector::operator[](int pos) const { return static_cast(this)->operator[](pos); } Note: Rvalue paramters are lvalues. Therefore to move-from them, they must be cast to rvalues using ``std::move()``: .. code-block:: cpp class Base { // snip... public: Base(const Base& b); Base(Base&& b); //snip... }; class Derived { // snip... public: Derived(const Derived& d); Derived(Derived&& d); //snip... }; Derived::Derived(Derived&& d) : Base(std::move(d)), ... {} To ensure the *Derived* move constructor invokes ``Base::Base(Base&&)``, *d* must first be cast to an rvalue using ``std::move(d)``. The reason why is explained in more detail below. Implementation of move constructor and move assignment operator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The move constructor and move assignment, both of which take rvalue references, both read from and write to the rvalue reference parameter. They perform a shallow copy of its resourses, and then, as in the example below, set the rvalue object\ |apos|\ s ``length`` to 0 and it\ |apos|\ s pointer p is set to ``nullptr`` to prevent the memory being deallocated when the rvalue's destructor is called. .. code-block:: cpp #include #include #include #include template class Vector { std::unique_ptr p; int size; int current; void grow(); static const int growth_factor = 2; static const int default_sz = 2; public: Vector() : p(std::make_unique(Vector::default_sz )), current{0} { } Vector(std::initializer_list lst) { for (auto& x : lst) { push_back(x); } } Vector(const Vector& lhs); Vector(Vector&& lhs); // move constructor Vector& operator=(const Vector& lhs); Vector& operator=(Vector&& lhs); // move assignment operator void push_back(const T& t); void push_back(T&& t); template void emplace_back(ARGS&& ... args); T& operator[](int); const T& operator[](int) const; std::ostream& print(std::ostream& ostr) const { std::copy(p.get(), p.get() + current, std::ostream_iterator(ostr, "\n")); return ostr; } friend std::ostream& operator<<(std::ostream& ostr, const Vector& vec) { return vec.print(ostr); } void* operator new (std::size_t size, void* ptr) noexcept; }; template inline Vector::Vector(const Vector& lhs) : p{new T[lhs.size]}, size{lhs.size}, current{lhs.current} { std::copy(p.get(), lhs.p, lhs.p + lhs.size); } template inline Vector::Vector(Vector&& lhs) : p(std::move(lhs.p)), size{lhs.size}, current{lhs.current} { lhs.size = 0; } template Vector& Vector::operator=(const Vector& lhs) { if (this != &lhs) { p = std::make_unique(new T[lhs.size]); size = lhs.size; copy(p, lhs.p, lhs.p + lhs.size); } return *this; } template Vector& Vector::operator=(Vector&& lhs) { if (this != &lhs) { p = std::move(lhs.p); // std::move() casts an lvalue to an rvalue. size = lhs.size; lhs.size = 0; } return *this; } template void Vector::grow() { auto new_size = size * Vector::growth_factor; std::unique_ptr ptr = std::make_unique(new_size); for (auto i = 0; i < size; ++i) { ptr[i] = std::move(p[i]); } size = new_size; p = std::move(ptr); ++current; } template void Vector::push_back(const T& t) { if (current == size) { grow(); } p[current++] = t; } template T& Vector::operator[](int pos) { if (pos < size && pos > 0) { return p[pos]; } else { throw(std::out_of_range("pos not in range.")); } } template const T& Vector::operator[](int pos) const { if (pos < size && pos > 0) { return const_cast(p[pos]); } else { throw(std::out_of_range("pos not in range.")); } } template void Vector::push_back(T&& t) { if (current == size) { grow(); } p[current++] = std::move( t ); } template template void Vector::emplace_back(ARGS&& ... args) { if (size == current) { grow(); } T *ptr = p.get(); T *location = ptr + current; new(location) T{std::forward(args)...}; current++; } Obviously the versions of the constructor and assignment operator overloaded to take an rvalue reference are faster that their copy constructor and copy assignment operator counterparts. Take for example .. code-block:: cpp Vector v1{1, 5, 12}; Vector v2{v1}; // invokes copy constructor Vector v3{v{2, 6, 16}}; // move constructor Vector::Vector(Vector&&) invoked // because an rvalue is passed template void f(Vector&& v); // forward declaration f(Vector{11, 19, 29}); // move constructor Vector::Vector(Vector&&) invoked *v2* above does not allocation any memory. Instead it "steals" the memory allocated by v1 by copying *v1*'s ``int *`` pointer and then setting *v1*'s pointer to *nullptr*. The same comments apply to *v3* which steals the memory allocated by the rvalue vector passed to it. Rvalue References and Derived classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Is an rvalue reference parameter an rvalue or an lvalue? Since an rvalue reference parameter has a name, it is not a temporary. It is therefore an lvalue. Since it "has a name" the rvalue reference parameter is an lvalue within the scope of its function. This implies move semantics in derived classes must be implemented in a certain way: .. code-block:: cpp class Base { char *p; int length; public: //...snip Base(Base&& lhs) { p = lhs.p; lhs.p = nullptr; length = p.length; p.length = 0; } //...snip }; Derived : public Base { public: Derived(Derived&& d) : Base(std::move(d)) {} }; Since ``d`` is an lvalue, the implementation of ``Derived(Derived&& d)`` requires casting ``d`` to an rvalue in order that the Base move constructor is invoked rather than the default copy constructor. Note, Since ``std::move()`` works correctly on both rvalues and lvalues, no harm is done when it is passed an rvalue: an rvalue is still returned. The g++ version of ``std::move()`` is discussed below. Before one can understand the implementation of *std::move()*, it is first necessary to understand forwarding references, which are discussed here :ref:`perfect-forwarding-label`. .. todo:: Is the section/discussion below part of forwarding references? Forwarding referencs need to first be understood to understand std::remove_reference and std::move!!!!! It takes an argument of generic type ``T&&``. While this looks like an rvalue reference, it works differently than an ordinary rvalue reference\ |mdash|\ say, for example, ``std::string&&``\ |mdash|\ where the parameter's type is specified. ``T&&`` binds to both lvalues and rvalues, and is known as a forwarding reference. When it binds to an lvalue, ``T`` resolves to an lvalue reference, and when an rvalue is passed **T** resolves to the underlying nonreference type. We can see this by implementing a version of ``Remove_reference`` and its partial template specializations that contains a static method called ``describe()``, which ``move()`` calls: .. code-block:: cpp template constexpr typename std::Remove_reference::type&& move(T&& __t) noexcept { return static_cast::type&&>(__t); } // Remove_reference defined template struct Remove_reference { static void describe() { cout << "In non-specialization Remove_reference<_Tp> constructor" << endl; } typedef _Tp type; }; // Remove_reference partial template specializations template struct Remove_reference<_Tp&> { static void describe() { cout << "In partial template specialization Remove_reference<_Tp&> constructor" << endl; } typedef _Tp type; }; template struct Remove_reference<_Tp&&> { static void describe() { cout << "In partial template specialization Remove_reference<_Tp&&> constructor" << endl; } typedef _Tp type; }; template constexpr typename Remove_reference::type&& move(T&& arg) { Remove_reference::describe(); return static_cast::type&&>(arg); } string a{"test"}; string&& rval = move(a); string {move(string{"xyz"})}; This results in the output:: In partial template specialization Remove_reference<_Tp&> constructor In non-specialization Remove_reference<_Tp> constructor In the case of ``string {move(string{"xyz"})};``, **T** resolves to ``std::string``. This is what is instantiated step-by-step: .. code-block:: cpp constexpr typename Remove_reference::type&& move(std::string&& arg) { Remove_reference::describe(); return static_cast::type&&>(arg); } which simplies to: .. code-block:: cpp constexpr typename std::string&& move(std::string&& arg) { Remove_reference::describe(); return static_cast(arg); } which is a rvalue cast (to something that does not have a name). In the case of ``move(a)``, **T** resolves to ``std::string&``. Again, this is what is instantiated step by step: .. code-block:: cpp constexpr typename Remove_reference::type&& move(std::string& && arg) { Remove_reference::describe(); return static_cast::type&&>(arg); } Applying the reference collapsing rules of C++11, gives us .. code-block:: cpp constexpr typename Remove_reference::type&& move(std::string& arg) { Remove_reference::describe(); return static_cast::type&&>(arg); } which simplies to .. code-block:: cpp constexpr std::string&& move(std::string& arg) { Remove_reference::describe(); return static_cast(arg); } Again as before, this casts arg to an rvalue reference that does not have a name. .. todo:: First discuss forwarding references before discussing std::move std::move() Implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~ Before one can understand the implementation of *std::move()*, it is first necessary to understand forwarding references, which are discussed here :ref:`perfect-forwarding-label`. .. code-block:: cpp template constexpr typename std::remove_reference::type&& move(T&& __t) noexcept { return static_cast::type&&>(__t); } Why is the return type of ``std::move()`` is ``constexpr typename std::remove_reference<_Tp>::type&&`` instead of ``T&&``? Recall that when an lvalue is passed to ``std::move()`` like below .. code-block:: cpp using namespace std; class X { //snip... public: X(const X&); X(X&&); //snip... }; X x1; X x2 = move(x1); that T binds as ``X&``, and the instantiation of ``move(x1)`` before reference collapsing is done looks like this .. code-block:: cpp constexpr typename std::remove_reference::type&& move(X& && __t) noexcept { return static_cast::type&&>(__t); } and after applying reference collapsing, it looks like this .. code-block:: cpp constexpr typename std::remove_reference::type&& move(X& __t) noexcept { return static_cast::type&&>(__t); } ``remove_reference::type`` is simply ``X``. Thus ``move(x1)`` resolves to be: .. code-block:: cpp constexpr X&& move(X& __t) noexcept { return static_cast(__t); } Had ``move()`` been implemented as .. code-block:: cpp template constexpr T&& move(T&& __t) noexcept { return static_cast(__t); } then it would have been instantiated as .. code-block:: cpp constexpr X& && move(X& && __t) noexcept { return static_cast(__t); } and after applying reference collapsing, we would have .. code-block:: cpp constexpr X& move(X& __t) noexcept { return static_cast(__t); } as the instantiation of ``move(x1)``, and the return value of ``move(x1)`` would still be an lvalue. If ``move()`` is passed an rvalue, then the instantion of .. code-block:: cpp template constexpr T&& move(T&& __t) noexcept { return static_cast(__t); } would work fine. For example: X createX(); X x = move(createX()); would instantiate .. code-block:: cpp constexpr X&& move(X&& __t) noexcept { return static_cast(__t); } and a nameless rvalue (known as a xvalue) would be returned. ``remove_reference::value&&`` is needed to ensure an lvalue is converted to an rvalue (or more specifically a xvalue). remove_reference_t ~~~~~~~~~~~~~~~~~~ C++14 introduced a shorthand or "synonym" for ``template typename remove_reference::type``, namely ``template remove_reference_t``, which is defined as: .. code-block:: cpp template using remove_reference_t = typename remove_reference::type We can use it to simplify the C++11 implementation of ``std::move()``, changing .. code-block:: cpp template constexpr typename std::remove_reference::type&& // C++11 implementation move(T&& __t) noexcept { return static_cast::type&&>(__t); } to .. code-block:: cpp template constexpr typename std::remove_reference_t&& // C++14 move(T&& __t) noexcept { return static_cast&&>(__t); } Move Conclusion ~~~~~~~~~~~~~~~ ``move(T&&)`` is non-overloaded function template that casts its argument to an rvalue. It works both with lvalue and rvalue arguments. It uses the partial template specializations provided by ``Remove_reference`` to do this.