Why move ctor is not called in this case?

77 Views Asked by At
#include <iostream>

using namespace std;

class Test {
public:
    
    Test(string value){
        cout<<"Ctor "<<value<<endl;
        _val=value;
    }
    Test( Test&& mv): _val(mv._val)
    {
        mv._val=string();
        cout<<"Mv constructor"<<endl;  
    }
    
    string& get()
    {
      return this->_val;
    }
private:
    string _val;
};

void print(Test&& t)
{
    cout<<"Stampa val is "<<t.get()<<endl;
}

int main()
{
    Test a{"ciao"};
    print(move(a));
    cout<<"Val of a is "<<a.get()<<endl;
    print(Test("test"));
    

    return 0;
}

The output of this is (adding line numbers to stdout):

Ctor ciao
Stampa val is ciao
Val of a is ciao
Ctor test
Stampa val is test

Why at line 2 in main the mv semantic is not called? I might understand at line four there is an optimisation so the constructor only is called but, I can't explain the first move. Any ideas?

2

There are 2 best solutions below

4
On

std::move just converts argument to rvalue (which could be moved later), it doesn't perform move operation itself. The converted rvalue is bound to the reference parameter t, so the move constructor isn't invoked in this case.

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object.

In particular, std::move produces an xvalue expression that identifies its argument t. It is exactly equivalent to a static_cast to an rvalue reference type.

Move constructor is typically called to initialize an object, it won't be called in reference binding (it's true for lvalue-reference too). If you change the parameter to be passed by-value, move constructor would be used (to initialize the parameter). E.g.

void print(Test t)
{
    cout<<"Stampa val is "<<t.get()<<endl;
}

LIVE


BTW: Even after change to pass-by-value print(Test("test")); doesn't invoke move constructor because of copy elision.

BTW2: In the move constructor it's better to move initialize data member val based on move operation provided by std::string. E.g.

Test( Test&& mv): _val(std::move(mv._val))
{
    cout<<"Mv constructor"<<endl;  
}
0
On

There are only two Test objects constructed in your code, not more.

int main()
{
    Test a{"ciao"};                          // here 
    print(move(a));
    cout<<"Val of a is "<<a.get()<<endl;
    print(Test("test"));                     // and here
}

The second also constructs the object via Test(string) constructor. std::move does not construct an object, it is merely a cast to a rvalue reference. Passing that rvalue reference to print also does not require to construct another Test. If you want to call the constructor you have to actually construct an instance, for example:

 auto t  = Test( std::move(a) );     // calls Test(Test&&)
 auto t2 = Test( Test("move me") );  // calls Test(string) then Test(Test&&)