Self-test 9:
C++11 features (rvalue reference and move semantics)

  1. If we have 2 (overloaded) functions with the following prototypes:

    f(const T&);
    f(T&&);

    When calling function f, if the parameter is of the following type, which one of the functions will be invoked?

    1. const rvalue
    2. const lvalue
    3. rvalue reference
    4. lvalue reference
    5. temporary value/object
    Solution

    "f(const T&)" will take all (1), (2), (3), (4) while "f(T&&)" will take (5).

    "f(T&&)" can only take temporary value/object. While "f(const T&)" can actually also accept temporary value/object as the parameter, the "f(T&&)" will have the priority to be chosen.


  2. True or false.

    The function std::move() performs the move operation for the given parameter.

    Solution
    False. The function std::move() actually does NOT move anything. It only does static casting. It is particularly useful to allow the use of a move constructor/assignment for a lvalue object by casting it to a rvalue reference of the object.

  3. Consider the following code:
    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
        A(int x) : n(x) { cout << "conversion " << n << endl; };
        // A(A&& a) : n(a.n) { cout << "move " << a.n << endl; } // what happens if you uncomment this line?
        A(const A& a) : n(a.n) { cout << "copy " << a.n << endl; }
    
    private:
        int n;
    };
    
    int main()
    {
        A a = 4;
        return 0;
    }
    

    What would be the output? And what happens if you uncomment the indicated line? In this question, it is assumed that the code is compiled with the flag -fno-elide-constructors.

    Solution
    "conversion 4 copy 4 " at first, and "conversion 4 move 4 " after uncommenting the indicated line. Without a move constructor, the copy constructor is invoked after using the conversion constructor to create a temporary A object out of the integer 4. With a move constructor defined, however, the move constructor will have the priority to be chosen.

  4. When are temporary objects destructed? Any special case?

    Solution

    Temporary objects are destructed at the end of the expression creating them, unless they are held by rvalue/const references. In the latter case, they live until the rvalue/const references go out-of-scope.


  5. Why/when is a move constructor useful? Why not always simply use a copy constructor?

    Solution
    It can be more efficient, in terms of both speed and memory usage.

    Consider a case where a class has some dynamically-allocated member objects. For that class, a deep-copy constructor is often needed. We may have a move constructor which will simply move (sometimes swap) resources from its input argument if it is a temporary object of the same class. It is more efficient as no memory allocation is needed. Since, originally, the temporary object is going to be discarded very soon anyway, why not make use of it by "stealing" its allocated resources?


  6. Assume both copy constructor and move constructor are defined. Please write down a line of code to call the move constructor with a locally created object x.

    	    Object x;
    	    _____________________ ; //create a new object y using move constructor here.
    	    
    Solution
    Object y(std::move(x));


  7. Study the following code and predict the output.

    
    #include <iostream>
    using namespace std;
    
    class A {
    public:
        A() { cout << "Default constructor" << endl;}
    
        void operator=(const A& a) { cout << "operator=(const &)" << endl; }
        void operator=(const A&& a) { cout << "operator=(temp obj)" << endl; }
    
        A(const A& a) { cout << "Copy constructor" << endl; *this = a;}
        A(A&& a) { cout << "Move constructor" << endl; *this = a;}
    };
    
    
    int main() {
        A x;
        A y(move(x));
    }
    	    
    Solution
    It prints Default constructor, Move constructor, and suprisingly = operator (const &).

    A&& a in the move constructor is an r-value reference which should be bound to an incoming temporary A object. However, once it is bound, a is treated like a "normal" object with both rvalue and lvalue. When it is passed to another function, here operator=(), it is treated as a "normal" object. Also note again (as have been tested in Q1) that operator=(const A&&) only accepts temporary objects.


  8. Consider the following code and put either a or move(a) into placeholders __i__, __ii__, __iii__ to make it compile without error.

    
    #include <iostream>
    using namespace std;
    
    class A {
    public:
        A () {}
        A (A&& a) { cout << "move consturctor" << endl; }
        A (const A& a) { cout << "copy consturctor" << endl; }
        A f(A&& a) { cout << "f(A&&)" << endl; return __i__; }
        A& g(A&& a) { cout << "g(A&&)" << endl; return __ii__; }
        A&& h(A&& a) { cout << "h(A&&)" << endl; return __iii__; }
    };
    
    
    int main() {
        A x;
        A y;
        x.f(move(y));
        x.g(move(y));
        x.h(move(y));
    }
    	    
    Solution
    __i__ : either move(a) or a
    __ii__ : a
    __iii__ : move(a)

    Function g needs to return an lvalue reference and move(a) casts a as an rvalue already. It cannot be bound to an lvalue reference. h needs to return an rvalue reference and A&& a is a reference and is an lvalue. For function f, it returns an object and either way works but different constructors will be used. A copy constructor will be called if it returns a. A move constructor will be called if it returns move(a).