Reputation: 1125
#include <iostream>
class C {
public:
~C() { std::cout << this << " destructor\n"; }
C() { std::cout << this << " constructor\n"; }
C(C&& rhs) {
std::cout << &rhs << " rhs\n";
std::cout << this << " move constructor\n";
}
C& operator=(C&& rhs) {
std::cout << &rhs << " rhs\n";
std::cout << this << " move assignment\n";
return *this;
}
};
C make_one() {
C tmp;
return tmp;
}
int main() {
std::cout << "move constructor:\n";
C c1(make_one());
std::cout << &c1 << " &c1\n\n";
std::cout << "move assignment:\n";
C c2;
c2 = make_one();
...
}
Output:
move constructor:
000000000021F9B4 constructor // tmp constructed in make_one()
000000000021F9B4 rhs // return triggers tmp being passed to ...
000000000021FA04 move constructor // ... c1's move constructor (see below)
000000000021F9B4 destructor // tmp destructs on going out of scope
000000000021FA04 &c1 // (confirmed c1's address)
move assignment:
000000000021FA24 constructor // c2 constructed
000000000021F9B4 constructor // tmp constructed in make_one() again
000000000021F9B4 rhs // tmp passed to ...
000000000021FA34 move constructor // ... a new object's move constructor
000000000021F9B4 destructor // tmp destructs on going out of scope
000000000021FA34 rhs // new object passed to ...
000000000021FA24 move assignment // .. c2's move assignment operator
000000000021FA34 destructor // new object destructs
...
The move assignment appears to trigger the move constructor first and create an extra object. Is this normal? I would have expected (by analogy with copy assignment) for tmp to be passed straight to c2's move assignment.
[Visual Studio Express 2013]
Upvotes: 3
Views: 745
Reputation: 1125
I finished off my example and leave it here in case it helps anyone else.
#include <iostream>
class C {
public:
~C() { std::cout << this << " destructor\n"; }
C() { std::cout << this << " constructor\n"; }
C(const C& rhs) {
std::cout << &rhs << " rhs\n";
std::cout << this << " copy constructor\n";
}
C& operator=(const C& rhs) {
std::cout << &rhs << " rhs\n";
std::cout << this << " copy assignment\n";
return *this;
}
C(C&& rhs) {
std::cout << &rhs << " rhs\n";
std::cout << this << " move constructor\n";
}
C& operator=(C&& rhs) {
std::cout << &rhs << " rhs\n";
std::cout << this << " move assignment\n";
return *this;
}
};
C make_one() {
C tmp;
return tmp;
}
int main() {
std::cout << "c1's constructor:\n";
C c1;
std::cout << "\nc1 passed to c2's copy constructor:\n";
C c2(c1);
std::cout << &c2 << " &c2\n\n";
std::cout << "c3 and c4's constructors:\n";
C c3, c4;
std::cout << "c3 passed to c4's copy assignment:\n";
c4 = c3;
std::cout << "\ntmp constructed, passed to c5's move constructor then destructed\n";
C c5(make_one());
std::cout << &c5 << " &c5\n\n";
std::cout << "c6's constructor:\n";
C c6;
std::cout << "tmp constructed, passed to return value's move constructor then destructed\n"
"return value passed to c6's move assignment then destructed:\n";
c6 = make_one();
return 0;
}
Debug Output:
c1's constructor:
000000000013F9B4 constructor
c1 passed to c2's copy constructor:
000000000013F9B4 rhs
000000000013F9D4 constructor
000000000013F9D4 &c2
c3 and c4's constructors:
000000000013F9F4 constructor
000000000013FA14 constructor
c3 passed to c4's copy assignment:
000000000013F9F4 rhs
000000000013FA14 copy assignment
tmp constructed, passed to c5's move constructor then destructed
000000000013F964 constructor
000000000013F964 rhs
000000000013FA34 move constructor
000000000013F964 destructor
000000000013FA34 &c5
c6's constructor:
000000000013FA54 constructor
tmp constructed, passed to return value's move constructor then destructed
return value passed to c6's move assignment then destructed:
000000000013F964 constructor
000000000013F964 rhs
000000000013FA64 move constructor
000000000013F964 destructor
000000000013FA64 rhs
000000000013FA54 move assignment
000000000013FA64 destructor
...
Release Output (showing elision of move constructor):
c1's constructor:
00000000001BFC41 constructor
c1 passed to c2's copy constructor:
00000000001BFC41 rhs
00000000001BFC40 constructor
00000000001BFC40 &c2
c3 and c4's constructors:
00000000001BFC78 constructor
00000000001BFC70 constructor
c3 passed to c4's copy assignment:
00000000001BFC78 rhs
00000000001BFC70 copy assignment
tmp constructed, passed to c5's move constructor then destructed
00000000001BFC68 constructor
00000000001BFC68 &c5
c6's constructor:
00000000001BFC60 constructor
tmp constructed, passed to return value's move constructor then destructed
return value passed to c6's move assignment then destructed:
00000000001BFC42 constructor
00000000001BFC42 rhs
00000000001BFC60 move assignment
00000000001BFC42 destructor
....
Upvotes: 0
Reputation: 141574
The "extra object" is called the return value. When returning a value from a function; this value is copy/move constructed from the value you supplied to the return
statement.
Often this undergoes copy elision which may explain why you didn't recognize it. When copy elision happens, the line C tmp;
will actually construct tmp
directly into the return value. Copy elision can also occur in some other situations; for the full text see C++11 [class.copy]#31.
Presumably you either manually disabled copy elision here, or the compiler decided it was a good idea not to perform copy elision. Update: Your compiler does this particular copy-elision only on Release builds - thanks Praetorian
Upvotes: 7