Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is moving a const returned object possible?

Lately, I have been reading this post and that post suggesting to stop returning const objects. This suggestion is also given by Stephan T. Lavavej in his talk in Going Native 2013.

I wrote a very simple test to help me understand which constructor/operator is called in all those cases:

  • Returning const or non const objects
  • What if Return Value Optimization (RVO) kicks in ?
  • What if Named Return Value Optimization (NRVO) kicks in ?

Here is the test:

#include <iostream>

void println(const std::string&s){
    try{std::cout<<s<<std::endl;}
    catch(...){}}

class A{
public:
    int m;
    A():m(0){println("    Default Constructor");}
    A(const A&a):m(a.m){println("    Copy Constructor");}
    A(A&&a):m(a.m){println("    Move Constructor");}
    const A&operator=(const A&a){m=a.m;println("    Copy Operator");return*this;}
    const A&operator=(A&&a){m=a.m;println("    Move Operator");return*this;}
    ~A(){println("    Destructor");}
};

A nrvo(){
    A nrvo;
    nrvo.m=17;
    return nrvo;}

const A cnrvo(){
    A nrvo;
    nrvo.m=17;
    return nrvo;}

A rvo(){
    return A();}

const A crvo(){
    return A();}

A sum(const A&l,const A&r){
    if(l.m==0){return r;}
    if(r.m==0){return l;}
    A sum;
    sum.m=l.m+r.m;
    return sum;}

const A csum(const A&l,const A&r){
    if(l.m==0){return r;}
    if(r.m==0){return l;}
    A sum;
    sum.m=l.m+r.m;
    return sum;}

int main(){
    println("build a");A a;a.m=12;
    println("build b");A b;b.m=5;
    println("Constructor nrvo");A anrvo=nrvo();
    println("Constructor cnrvo");A acnrvo=cnrvo();
    println("Constructor rvo");A arvo=rvo();
    println("Constructor crvo");A acrvo=crvo();
    println("Constructor sum");A asum=sum(a,b);
    println("Constructor csum");A acsum=csum(a,b);
    println("Affectation nrvo");a=nrvo();
    println("Affectation cnrvo");a=cnrvo();
    println("Affectation rvo");a=rvo();
    println("Affectation crvo");a=crvo();
    println("Affectation sum");a=sum(a,b);
    println("Affectation csum");a=csum(a,b);
    println("Done");
    return 0;
}

And Here is the output in release mode (with NRVO and RVO):

build a
    Default Constructor
build b
    Default Constructor
Constructor nrvo
    Default Constructor
Constructor cnrvo
    Default Constructor
Constructor rvo
    Default Constructor
Constructor crvo
    Default Constructor
Constructor sum
    Default Constructor
    Move Constructor
    Destructor
Constructor csum
    Default Constructor
    Move Constructor
    Destructor
Affectation nrvo
    Default Constructor
    Move Operator
    Destructor
Affectation cnrvo
    Default Constructor
    Copy Operator
    Destructor
Affectation rvo
    Default Constructor
    Move Operator
    Destructor
Affectation crvo
    Default Constructor
    Copy Operator
    Destructor
Affectation sum
    Copy Constructor
    Move Operator
    Destructor
Affectation csum
    Default Constructor
    Move Constructor
    Destructor
    Copy Operator
    Destructor
Done
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor
    Destructor

What I don't understant is this: why is the move constructor used in the "Constructor csum" test ?

The return object is const so I really feel like it should call the copy constructor.

What am I missing here ?

It should not be a bug from the compiler, both Visual Studio and clang give the same output.

like image 250
Arnaud Avatar asked Dec 12 '13 13:12

Arnaud


People also ask

Can a const object be moved?

A move constructor requires the modification of the original object and since that object is const you cannot move so it calls the copy constructor instead.

Can you move a const variable?

Expert #2: “You cannot move from a variable marked as const, and instead the copy-constructor/assignment will be invoked more often.

Does Returning an object copy it c++?

The copy constructor is invoked when a temporary object is created as the result of a function returning an object.


1 Answers

What I don't understand is this: why is the move constructor used in the "Constructor csum" test ?

In this particular case the compiler is allowed to do [N]RVO, but it did not do it. The second best thing is to move-construct the returned object.

The return object is const so I really feel like it should call the copy constructor.

That does not matter at all. But I guess that is not completely obvious, so lets walk through what it conceptually mean to return a value, and what [N]RVO is. For that, the simplest approach is to ignore the returned object:

T f() {
   T obj;
   return obj;   // [1] Alternatively: return T();
}
void g() {
   f();          // ignore the value
}

This in the line marked as [1] there is a copy from the local/temporary object to the returned value. Even if the value is completely ignored. That is what you are exercising in the code above.

If you don't ignore the returned value, as in:

T t = f();

there is conceptually a second copy from the returned value to the t local variable. That second copy is being elided in all of your cases.

For the first copy, whether the object being returned is const or not does not matter, the compiler determines what to do based on the arguments to the [conceptual copy/move] constructor, not whether the object being constructed will be const or not. This is the same as:

// a is convertible to T somehow
const T ct(a);
T t(a);

Whether the destination object is const or not does not matter, the compiler needs to find the best constructor based on the arguments, not the destination.

Now if we take that back to your exercise, to make sure that the copy constructor is not called, you need to modify the argument to the return statement:

A force_copy(const A&l,const A&r){ // A need not be `const`
    if(l.m==0){return r;}
    if(r.m==0){return l;}
    const A sum;
    return sum;
}

That should trigger copy construction, but then again it is simple enough that the compiler may elide the copy altogether if it finds it fit.

like image 54
David Rodríguez - dribeas Avatar answered Oct 27 '22 03:10

David Rodríguez - dribeas