The Standard provides an example regarding to the a move constructor. There is what it says:
A non-template constructor for class X is a move constructor if its
first parameter is of type X&&, const X&&, volatile X&&, or const
volatile X&&, and either there are no other parameters or else all
other parameters have default arguments (8.3.6).
I was trying to run some an experiments with an example the Stadard provides:
#include <iostream>
#include <limits>
struct Y {
Y(){ std::cout << "Y()" << std::endl; };
Y(const Y&){ std::cout << "Y(const Y&)" << std::endl; };
Y(Y&&){ std::cout << "Y(const Y&&)" << std::endl; };
};
Y f(int)
{
return Y();
}
Y d(f(1)); // calls Y(Y&&)
Y e = d; // calls Y(const Y&)
int main(){ }
DEMO
But instead copy constructor was called. Why that?
The copy-constructor is invoked by the line:
Y e = d;
This cannot be a move operation , because d is an lvalue. This is why you see the copy constructor call in your output.
For the line Y d(f(1)), d is moved from the rvalue f(1) (which was in turn moved from Y()), however copy elision means that the output of both of these move-constructors may be suppressed.
Related
I have a C++ grammar question related to X x = X();, which results in the move constructor being invoked using special compilation flags below.
Is the grammar not in a form which one would expect the move assignment operator to be called? There is an rvalue on the right hand side of the equals sign. Curious if there is some reason behind this or history.
Also curious why commenting out the move constructor results in the copy constructor is called?
Understood that without the special flags this results in copy elision. Thanks
#include <iostream>
using namespace std;
struct X {
int x;
X() : x{0} { cout << "def cons" << endl; }
X(const X& xx) : x{xx.x} { cout << "copy cons" << endl; }
X(X&& xx) : x{xx.x} { xx.x = 0; cout << "move cons" << endl; }
X& operator=(const X& xx) { cout << "assign op" << endl; return *this; }
X& operator=(X&& xx) { xx.x = 0; cout << "move assign" << endl; return *this; }
};
int main(int argc, char *argv[])
{
X x = X();
return 0;
}
Compilation and results:
g++ -pedantic -fno-elide-constructors -Wall test145.cc && ./a.out
def cons
move cons
The object does not exist yet, it is being constructed, therefore requires a constructor. In this case the move constructor is called because of an rvalue on the RHS. A temporary object is constructed using the default constructor and this temporary object is then used to construct the object on the LHS using the move constructor.
If the class has no move constructor, then the copy constructor is called for backwards compatibility. const-lvalue-reference of the copy constructor can bind to both lvalues and rvalues, so the copy constructor is chosen in lack of a move constructor.
The move assignment operator is not called because a move assignment takes form <expr> = <expr>. In the case of this post X x = X() is used, which is not an assignment, but is a declaration that initializes x without using assignment. In this case, it’s a move construction.
Let's take the following C++ sample:
#include <iostream>
struct X
{
std::string s;
X() : s("X") { }
X(const X& other) : s{other.s} { std::cout << "cpy-ctor\n"; }
X(X&& o): s{o.s} { o.s = ""; std::cout << "move-ctor\n"; }
X& operator=(const X& other) {
std::cout << "cpy-assigned\n";
s = other.s;
return *this;
}
X& operator=(X&& other) {
if (this != &other) {
s = other.s;
other.s = "";
}
std::cout << "move assigned\n";
return *this;
}
};
X f(X x) {
std::cout << "f: ";
return x;
}
X g() {
std::cout << "g: ";
X x;
return x;
}
int main() {
X x;
X y;
x = f(X());
y = g();
}
If I compile it with gcc 4.8.2, I have the following result:
f: move-ctor
move assigned
g: move assigned
I do not understand why copy-constructor is not called when I am calling the g function.
I am just trying to understand when the copy or the move constructors are called.
Although you are correct to identify that there is, logically, a copy/move of the local variable x from inside g() when it is returned, a useful feature of C++ is that it can elide (i.e. skip) this operation in many cases, even when the copy/move would have side-effects. This is one of those cases. When performed, this is known as the named return value optimisation.
It's arguably less useful than it was before we had move semantics, but it's still nice to have. Indeed, C++17 made it mandatory in some (select) cases.
In C++ all the expressions are:
lvalue
prvalue
xvalue
Constructing an object
- lvalue
Y x{};
Y y{x}; // copy constructor, x is an lvalue
- prvalue - RVO
With RVO, which is enabled by gcc by default. This elides using the copy constructor and instead the object is constructed once.
X g()
{
X x {};
x.value = 10;
return x;
}
X y {g()}; // X constructor get's called only once to create "y". Also
// y is passed a a reference to g() where y.value = 10.
// No copy/move constructor for optimization "as if" rule
- prvalue - No RVO
Without RVO, in this case it depends. If the move constructors are explicitly or implicitly deleted, then it will call the copy constructor
Copy constructor
struct X { X(const X&) {}}; // implicitly deletes move constructor
// and move assignment, see rule of 5
X g()
{
return X{}; // returns a prvalue
}
X y {g()}; // prvalue gets converted to xvalue,
// "temporary materialization", where the xvalue has an
// identity where members can be copied from. The xvalue
// binds to lvalue reference, the one from copy constructor
// argument
Move constructor
X { X(X&&) {}}; // explicitly declared move constructor
X g()
{
return X{}; // returns a prvalue
}
X y {g()}; // prvalue gets converted to xvalue,
// "temporary materialization", where the xvalue has an
// identity where members can be moved from. The xvalue
// binds to rvalue reference, the one from move constructor
// argument
- xvalue
X x {};
X y {std::move(x)}; // std::move returns an xvalue, where if move
// constructor is declared will call it, other wise
// copy constructor, similar to explained above for
// prvalue.
Copy/move assignment
- lvalue
X x{};
X y{};
x = y; // call copy assignment operator since y is an lvalue.
- prvalue
If the move assignments are explicitly or implicitly deleted, then it will call the copy assignment operator.
Copy assignment
struct X{ X& operator=(const X&); } // implicilty deletes move
// constructor and move assignment,
// see rule of 5
X g()
{
return X{}; // returns a prvalue
}
x = g(); // prvalue gets converted to xvalue,
// "temporary materialization", where the xvalue has an identity
// where members can be copied from. The xvalue binds to lvalue
// reference, the one from copy assignment operator argument
Move assignment
struct X{ X& operator=(X&&); } // explicitly declared move assignment operator
X g()
{
return X{}; // returns a prvalue
}
x = g(); // prvalue gets converted to xvalue,
// "temporary materialization", where the xvalue has an identity
// where members can be moved from. The xvalue binds to rvalue
// reference, the one from move assignment operator argument
- xvalue
X x {};
X y {};
x = std::move(x); // std::move returns an xvalue, where if move
// assignment is declared will call it, other
// wise copy assignment, similar to explained
// above for prvalue.
A copy constructor is called when you instantiate an object using another object of the same type.
For example:
X x;
X y(x);
The last line in your code assigns a value returned from a function to an already constructed object. This is done via move assignment.
I wrote the following program to test when the copy constructor is called and when the assignment operator is called:
#include
class Test
{
public:
Test() :
iItem (0)
{
std::cout << "This is the default ctor" << std::endl;
}
Test (const Test& t) :
iItem (t.iItem)
{
std::cout << "This is the copy ctor" << std::endl;
}
~Test()
{
std::cout << "This is the dtor" << std::endl;
}
const Test& operator=(const Test& t)
{
iItem = t.iItem;
std::cout << "This is the assignment operator" << std::endl;
return *this;
}
private:
int iItem;
};
int main()
{
{
Test t1;
Test t2 = t1;
}
{
Test t1;
Test t2 (t1);
}
{
Test t1;
Test t2;
t2 = t1;
}
}
This results in the following output (just added empy lines to make it more understandable):
doronw#DW01:~$ ./test
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the default ctor
This is the assignment operator
This is the dtor
This is the dtor
The second and third set behave as expected, but in the first set the copy constructor is called even though the assignment operator is used.
Is this behaviour part of the C++ standard or just a clever compiler optimization (I am using gcc 4.4.1)
No assignment operator is used in the first test-case. It just uses the initialization form called "copy initialization". Copy initialization does not consider explicit constructors when initializing the object.
struct A {
A();
// explicit copy constructor
explicit A(A const&);
// explicit constructor
explicit A(int);
// non-explicit "converting" constructor
A(char const*c);
};
A a;
A b = a; // fail
A b1(a); // succeeds, "direct initialization"
A c = 1; // fail, no converting constructor found
A d(1); // succeeds
A e = "hello"; // succeeds, converting constructor used
Copy initialization is used in those cases that correspond to implicit conversions, where one does not explicitly kick off a conversion, as in function argument passing, and returning from a function.
C++ standard 8.5/12
The initialization that occurs in
argument passing, function return,
throwing an exception (15.1), handling
an exception (15.3), and
brace-enclosed initializer lists
(8.5.1) is called copy-initialization
and is equivalent to the form
T x = a;
The initialization that occurs in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional
notation type conversions (5.2.3), and
base and member initializers (12.6.2)
is called direct-initialization and is
equivalent to the form
T x(a);
Your first set is according to the C++ standard, and not due to some optimization.
Section 12.8 ([class.copy]) of the C++ standard gives a similar example:
class X {
// ...
public:
X(int);
X(const X&, int = 1);
};
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
The last line would be the one matching your case.
Why does gcc required copy constructor for implicit conversion constructor call?
class X
{
public:
X(int q) {}
~X()
{
std::cout << "~X()" << std::endl;
}
X(const X&) = delete;
};
X x = 1; // gives error: use of deleted function ‘X::X(const X&)’
What is more interesting here that if I even write copy constructor it is not called. Destructor is called only once, so the following code
class X
{
public:
X(int q) {}
~X()
{
std::cout << "~X()" << std::endl;
}
X(const X&)
{
std::cout << "copy ctor" << std::endl;
}
};
int main()
{
X x = 1;
}
prints ~X()
Is it bug? Is there any workaround?
gcc version on my locaL PC is 4.6.3, this also works the same way on another gcc version (online)
http://ideone.com/ustDRj
X x = 1; is the syntax for copy initialisation. In case the initialiser is not of type X (as here), this is semantically equivalent to this:
X x(X(1));
That is, it constructs an insance of X from argument 1, and then copy-initialises x from that instance.
Just like any other copy initialisation, the copy can be elided. This probably happens in your case, so the copy constructor is not actually called. Still, (again, just like with any other copy elision), it must be available.
you are trying to initialize a value to the class object here which calls the copy-constructor.
int main()
{
X x = 1;
}
change the code to
X x(1);
I wrote the following program to test when the copy constructor is called and when the assignment operator is called:
#include
class Test
{
public:
Test() :
iItem (0)
{
std::cout << "This is the default ctor" << std::endl;
}
Test (const Test& t) :
iItem (t.iItem)
{
std::cout << "This is the copy ctor" << std::endl;
}
~Test()
{
std::cout << "This is the dtor" << std::endl;
}
const Test& operator=(const Test& t)
{
iItem = t.iItem;
std::cout << "This is the assignment operator" << std::endl;
return *this;
}
private:
int iItem;
};
int main()
{
{
Test t1;
Test t2 = t1;
}
{
Test t1;
Test t2 (t1);
}
{
Test t1;
Test t2;
t2 = t1;
}
}
This results in the following output (just added empy lines to make it more understandable):
doronw#DW01:~$ ./test
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the copy ctor
This is the dtor
This is the dtor
This is the default ctor
This is the default ctor
This is the assignment operator
This is the dtor
This is the dtor
The second and third set behave as expected, but in the first set the copy constructor is called even though the assignment operator is used.
Is this behaviour part of the C++ standard or just a clever compiler optimization (I am using gcc 4.4.1)
No assignment operator is used in the first test-case. It just uses the initialization form called "copy initialization". Copy initialization does not consider explicit constructors when initializing the object.
struct A {
A();
// explicit copy constructor
explicit A(A const&);
// explicit constructor
explicit A(int);
// non-explicit "converting" constructor
A(char const*c);
};
A a;
A b = a; // fail
A b1(a); // succeeds, "direct initialization"
A c = 1; // fail, no converting constructor found
A d(1); // succeeds
A e = "hello"; // succeeds, converting constructor used
Copy initialization is used in those cases that correspond to implicit conversions, where one does not explicitly kick off a conversion, as in function argument passing, and returning from a function.
C++ standard 8.5/12
The initialization that occurs in
argument passing, function return,
throwing an exception (15.1), handling
an exception (15.3), and
brace-enclosed initializer lists
(8.5.1) is called copy-initialization
and is equivalent to the form
T x = a;
The initialization that occurs in new
expressions (5.3.4), static_cast
expressions (5.2.9), functional
notation type conversions (5.2.3), and
base and member initializers (12.6.2)
is called direct-initialization and is
equivalent to the form
T x(a);
Your first set is according to the C++ standard, and not due to some optimization.
Section 12.8 ([class.copy]) of the C++ standard gives a similar example:
class X {
// ...
public:
X(int);
X(const X&, int = 1);
};
X a(1); // calls X(int);
X b(a, 0); // calls X(const X&, int);
X c = b; // calls X(const X&, int);
The last line would be the one matching your case.