Reputation: 25
I have a simple struct, that has all constructors defined. It has an int variable, each constructor and assign operator prints address of *this, current value of int and a new value of int. Move and copy assign operators and constructors also print adress of passed value.
#include <iostream>
struct X
{
int val;
void out(const std::string& s, int nv, const X* from = nullptr)
{
std::cout<<this<<"->"<<s<<": "<<val<<" ("<<nv<<")";
if (from)
std::cout<<", from: ["<<from<<"]";
std::cout<<"\n";
}
X(){out("simple ctor X()",0); val = 0;}
X(int v){out("int ctor X(int)", v);val = v; }
X(const X& x){out("copy ctor X(X&)", x.val, &x);val = x.val; };
X&operator = (const X& x){out("copy X::operator=()", x.val, &x); val = x.val; return *this;}
~X(){out("dtor ~X", 0);}
X&operator = (X&& x){out("move X::operator(&&)", x.val, &x); val = x.val; return *this;}
X(X&& x){out("move ctor X(&&x)", x.val, &x);val = x.val;}
};
X copy(X a){return a;}
int main(int argc, const char * argv[]) {
X loc{4};
X loc2;
std::cout<<"before copy\n";
loc2 = copy(loc);
std::cout<<"copy finish\n";
}
output:
0xffdf7278->int ctor X(int): 134523184 (4) 0xffdf727c->simple ctor X(): 134514433 (0) before copy 0xffdf7280->copy ctor X(X&): 1433459488 (4), from: [0xffdf7278] 0xffdf7284->move ctor X(&&x): 1433437824 (4), from: [0xffdf7280] 0xffdf727c->move X::operator(&&): 0 (4), from: [0xffdf7284] 0xffdf7284->dtor ~X: 4 (0) 0xffdf7280->dtor ~X: 4 (0) copy finish 0xffdf727c->dtor ~X: 4 (0) 0xffdf7278->dtor ~X: 4 (0)
What's the purpose of creating an additional object with (in this example) address 0xffdf7284?
Upvotes: 2
Views: 58
Reputation: 76297
If you look at the copy elision rules from cppreference.com, you can notice that there are two case where the compilers are required to omit the copy- and move- constructors of class objects even if copy/move constructor and the destructor have observable side-effects (which yours do, due to the printouts). The first is clearly irrelevant to this case. The second is
In a function call, if the operand of a return statement is a prvalue and the return type of the function is the same as the type of that prvalue.
With the example given of:
T f() { return T{}; }
T x = f();
This seems more relevant, however, note that in your case, the operand of the return
statement is not a prvalue. So in this case, no mandatory elision applies.
The set of steps, when callingloc2 = copy(loc);
, is as follows:
a
is copy-constructed from loc
.a
.loc2
is move-assigned from the return value.Logically, a person could look at the code and deduce that fewer operations need to be done (in particular, when looking at copy
, it's obvious that, logically, an assignment from loc
to loc2
is enough), but the compiler doesn't know that the purpose of your code isn't to generate the side effects (the printouts), and it is not breaking any rules here.
Upvotes: 2