Zurab Kargareteli
Zurab Kargareteli

Reputation: 295

c++11 move constructor, initializing by unknown method using rvalue

I've made this TestClass to better illustrate the problem. As you can see the move constructor is called when the object is pushed into the vector and when the constructor is initialized using std::move function. But when we call TestClass testvar2(rvalue_func()); you can see at the end, that the result values of the rvalue_func() are assigned to testvar2 object, but the move constructor isn't called, also no other constructors or assignment operators were called...

The question: How did it copy the values from rvalue_func() to testvar2 without calling anything and why the move constructor wasn't called ?

#include <iostream>
using std::cout;
using std::endl;

#include <vector>

class TestClass {
public:

    TestClass(int arg_x=0, int arg_y=0) :
            values { nullptr } {

        values = new int[2];
        values[0] = arg_x;
        values[1] = arg_y;
        cout << "default constructor " << "x = " << values[0] << " y = "
                << values[1] << endl;
    }

    TestClass(const TestClass &arg) :
            values { nullptr } {

        values = new int[2];

        values[0] = arg.values[0];
        values[1] = arg.values[1];

        cout << "copy constructor " << "x = " << values[0] << " y = "
                << values[1] << endl;
    }

    TestClass(TestClass &&arg) :
            values { arg.values } {
        arg.values = nullptr;
        cout << "move constructor " << "x = " << values[0] << " y = "
                << values[1] << endl;
    }

    TestClass &operator=(TestClass &right) {
        cout << "assignment operator =" << endl;
        if (this != &right) {
            delete values;
            values = nullptr;
            values = new int[2];
            values[0] = right.values[0];
            values[1] = right.values[2];
        }
        return *this;
    }

    TestClass &operator=(TestClass &&right) {
        cout << "move assignment operator =" << endl;
        if (this != &right) {
            delete values;
            values = right.values;
            right.values = nullptr;
        }
        return *this;
    }

    void print() {
        if (values != nullptr)
            cout << "x = " << values[0] << " y = " << values[1] << endl;
    }
private:
    int *values;
};

TestClass rvalue_func() {
    cout << "creating TestClass temp" << endl;
    TestClass temp(100, 200);
    cout << "TestClass temp is created" << endl;
    return temp;
}
void test_rvalues() {
    cout << "-------------vector push back--------------" << endl;
    std::vector<TestClass> test_vector;
    test_vector.push_back(TestClass(1, 2));

    cout << "-----rvalue constructor with std::move-----" << endl;
    TestClass testvar1(std::move(rvalue_func()));

    cout << "------------rvalue constructor-------------" << endl;
    TestClass testvar2(rvalue_func());


    cout << "-------------------------------------------" << endl;
    cout << "testvar2 values ";
    testvar2.print();
}

int main(int argc, char *argv[]) {
    test_rvalues();

    return 0;
}

Results:

-------------vector push back--------------
default constructor x = 1 y = 2
move constructor x = 1 y = 2
-----rvalue constructor with std::move-----
creating TestClass temp
default constructor x = 100 y = 200
TestClass temp is created
move constructor x = 100 y = 200
------------rvalue constructor-------------
creating TestClass temp
default constructor x = 100 y = 200
TestClass temp is created
-------------------------------------------
testvar2 values x = 100 y = 200

Upvotes: 0

Views: 84

Answers (1)

Daniel H
Daniel H

Reputation: 7443

This is an optimization that compilers are allowed to perform, called copy elision (this seems to be the name even when it’s the move constructor which is elided).

Basically, the compiler is sometimes allowed (or, since C++17, even required) to not call a copy or move constructor, if instead it can just create the object in the place it will be copied or moved to. In this case, it knew that the object would go into testvar2, so it just created the object there in the first place.

Usually compiler optimizations are only allowed as long as a conforming program has no way to tell the difference between the optimization being present and it not being present (for example, replacing arithmetic operations on ints with other ones that produce the same result but are cheaper for the CPU to compute). Copy elision is one of the few cases where the compiler is specifically allowed to optimize in a way you can tell the difference.

Upvotes: 1

Related Questions