This question already has answers here:
What are copy elision and return value optimization?
(5 answers)
Closed 8 years ago.
There is a part of C++ code I don't really understand.
Also I don't know where should I go to search information about it, so I decided to ask a question.
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test();
Test(Test const & src);
Test& operator=(const Test& rhs);
Test test();
int x;
};
Test::Test()
{
cout << "Constructor has been called" << endl;
}
Test::Test(Test const & src)
{
cout << "Copy constructor has been called" << endl;
}
Test& Test::operator=(const Test& rhs)
{
cout << "Assignment operator" << endl;
}
Test Test::test()
{
return Test();
}
int main()
{
Test a;
Test b = a.test();
return 0;
}
Why the input I get is
Constructor has been called
Constructor has been called
?
a.test() creates a new instance by calling "Test()" so that's why the second message is displayed. But why no copy constructor or assignment called?
And if I change "return Test()" to "return *(new Test())" then the copy constructor is called.
So why isn't it called the first time?
Compilers are very smart. Both copies - returning from test and initialising b (not this is not an assignment) - are elided according to the following rule (C++11 §12.8):
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
Compilers are allowed to do this even if it would change the behaviour of your program (like removing your output messages). It's expected that you do not write copy/move constructors and assignment operators that have other side effects.
Note that is only one of four cases in which copy elision can occur (not counting the as-if rule).
The call to a.test() returns by value and this value is then assigned to b "copying" the return value. This invokes the copy constructor.
Related
This question already has answers here:
Overload resolution between object, rvalue reference, const reference
(1 answer)
Overloading for pass-by-value and pass-by-rvalue-reference
(1 answer)
Pass by value vs pass by rvalue reference
(7 answers)
Closed 9 days ago.
I was trying a move constructor example as shown below:
#include <iostream>
#include <string.h>
using namespace std;
class Class1
{
string name;
public:
//Parameterized ctor
Class1(string nm): name(nm)
{
cout <<"\nClass1 Parameterized Constructor" << endl;
}
//move ctor
Class1(string&& other): name(std::move(other))
{
cout <<"\nClass1 Move Constructor" << endl;
}
};
class Class2
{
Class1 class1_obj;
public:
//Parameterized ctor
Class2(Class1 param): class1_obj(param)
{
cout <<"\nClass2 Parameterized Constructor" << endl;
}
//move ctor
Class2(Class1&& param): class1_obj(std::move(param))
{
cout <<"\nClass2 Move Constructor" << endl;
}
};
int main()
{
string obj7("Suraj");
Class1 obj6(obj7);
Class2 obj5(move(obj6));
return 0;
}
Compiler is generating following error:
error: call of overloaded 'Class2(std::remove_reference<Class1&>::type)' is ambiguous
Class2 obj5(move(obj6));
I changed the Class2 parameterized ctor by adding a reference to the parameter in the definition as:
Class2(Class1& param): class1_obj(param)
{
cout <<"\nClass2 Parameterized Constructor" << endl;
}
What is the reason for this error?
Is the solution correct, and if yes, what should be done to declare the same ctor without a reference?
It is not possible to overload on by-value vs by-reference. That is always ambiguous. That's simply how the language was designed. It would be hard to make a useful general choice between the two.
If you want to handle rvalues specifically, then let the first overload of the constructor take const Class1& instead of Class1.
However, in most cases just a single by-value constructor is good enough:
Class2(Class1 param): class1_obj(std::move(param))
{ /*...*/ }
This will also use the move constructor instead of the copy constructor where possible. The only negative is that is might use one extra move construction in the end, which is normally not a problem.
(The same issue applies to Class1's constructors by the way. You just happened to test it only with lvalue arguments, where the rvalue reference overload is non-viable. If you try it with a rvalue, then it will cause the same problem.)
I am trying to get the grasp of rvalue references and move semantics with a simple self-made example but I can't understand a specific part. I have created the following class:
class A {
public:
A(int a) {
cout << "Def constructor" << endl;
}
A(const A& var) {
cout << "Copy constructor" << endl;
}
A(A&& var) {
cout << "Move constructor" << endl;
}
A& operator=(const A& var) {
cout << "Copy Assignment" << endl;
return *this;
}
A& operator=(A&& var) {
cout << "Move Assignment" << endl;
return *this;
}
};
I tried the following experiments to see if I can predict how the constructors/operators are going to be called:
A a1(1) - The default constructor is going to be called.
PREDICTED.
A a2 = a1 - The copy constructor is going to be called. PREDICTED.
a1 = a2 - The copy assignment operator is going to be called.
PREDICTED.
Now, I created a simple function that just returns an A object.
A helper() {
return A(1);
}
A a3 = helper() - The default constructor is going to be called in
order to create the object that the helper returns. The move
constructor is not going to be called due to RVO. PREDICTED.
a3 = helper() - The default constructor is going to be called in
order to create the object that the helper returns. Then, the move
assignment operator is going to be called. PREDICTED.
Now comes the part I don't understand. I created another function that is completely pointless. It takes an A object by value and it just returns it.
A helper_alt(A a) {
return a;
}
A a4 = helper_alt(a1) - This will call the copy constructor, to
actually copy the object a1 in the function and then the move
constructor. PREDICTED.
a4 = helper_alt(a1) - This will call the copy constructor, to
actually copy the object a1 in the function and then I thought that
the move assignment operator is going to be called BUT as I saw,
first, the move constructor is called and then the move assignment
operator is called. HAVE NO IDEA.
Please, if any of what I said is wrong or you feel I might have not understood something, feel free to correct me.
My actual question: In the last case, why is the move constructor being called and then the move assignment operator, instead of just the move assignment operator?
Congratulations, you found a core issue of C++!
There are still a lot of discussions around the behavior you see with your example code.
There are suggestions like:
A&& helper_alt(A a) {
std::cout << ".." << std::endl;
return std::move(a);
}
This will do what you want, simply use the move assignment but emits a warning from g++ "warning: reference to local variable 'a' returned", even if the variable goes immediately out of scope.
Already other people found that problem and this is already made a c++ standard language core issue
Interestingly the issue was already found in 2010 but not solved until now...
To give you an answer to your question "In the last case, why is the move constructor being called and then the move assignment operator, instead of just the move assignment operator?" is, that also C++ committee does not have an answer until now. To be precise, there is a proposed solution and this one is accepted but until now not part of the language.
From: Comment Status
Amend paragraph 34 to explicitly exclude function parameters from copy elision. Amend paragraph 35 to include function parameters as eligible for move-construction.
consider the below example. I have compiled the sample code using -fno-elide-constructors flag to prevent RVO optimizations:
g++ -fno-elide-constructors -o test test.cpp
#include<iostream>
using namespace std;
class A {
public:
A(int a) {
cout << "Def constructor" << endl;
}
A(const A& var) {
cout << "Copy constructor" << endl;
}
A(A&& var) {
cout << "Move constructor" << endl;
}
A& operator=(const A& var) {
cout << "Copy Assignment" << endl;
return *this;
}
A& operator=(A&& var) {
cout << "Move Assignment" << endl;
return *this;
}
};
A a_global(1);
A helper_alt(A a) {
return a;
}
A helper_a_local(A a) {
A x(1);
return x;
}
A helper_a_global(A a) {
return a_global;
}
int main(){
A a1(1);
A a4(4);
std::cout << "================= helper_alt(a1) ==================" << std::endl;
a4 = helper_alt(a1);
std::cout << "=============== helper_a_local() ================" << std::endl;
a4 = helper_a_local(a1);
std::cout << "=============== helper_a_global() ================" << std::endl;
a4 = helper_a_global(a1);
return 0;
}
This will result in the below output:
Def constructor
Def constructor
Def constructor
================= helper_alt(a1) ==================
Copy constructor
Move constructor
Move Assignment
=============== helper_a_local() ================
Copy constructor
Def constructor
Move constructor
Move Assignment
=============== helper_a_global() ================
Copy constructor
Copy constructor
Move Assignment
In simple words, C++ constructs a new temporary object (rvalue) when the return type is not a reference, which results in calling Move or Copy constructor depending on the value category and the lifetime of the returned object.
Anyway, I think the logic behind calling the constructor is that you are not working with reference, and returned identity should be construed first, either by copy or move constructor, depending on the returned value category or lifetime of the return object. As another example:
A helper_move_vs_copy(A a) {
// Call the Copy Constructor
A b = a;
// Call the Move Constructor, Due to the end of 'a' lifetime
return a;
}
int main(){
A a1(1);
A a2(4);
std::cout << "=============== helper_move_vs_copy() ================" << std::endl;
helper_move_vs_copy(a1);
return 0;
}
which outputs:
Def constructor
Def constructor
=============== helper_move_vs_copy() ================
Copy constructor
Copy constructor
Move constructor
From cppreference:
an xvalue (an “eXpiring” value) is a glvalue that denotes an object whose resources can be reused;
At last, it is the job of RVO to decrease unnecessary moves and copies by optimization of the code, which can even result in an optimized binary for basic programmers!
Consider this code:
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;
struct BigInteger
{
vector<int> arr;
BigInteger()
{
cout << "default constructor" << endl;
this->arr.push_back(0);
}
BigInteger(initializer_list<int> il)
{
cout << "initializer_list constructor" << endl;
for (const int x : il)
{
arr.push_back(x);
}
}
BigInteger(const BigInteger& obj) //copy constructor
{
cout << "copy constructor" << endl;
this->arr = obj.arr;
}
BigInteger(BigInteger&& obj) //move constructor
{
cout << "move constructor" << endl;
swap(*this, obj);
}
~BigInteger() //destructor
{
cout << "destructor" << endl;
arr.clear(); //probably because of RAII, I guess I don't have to write this
}
BigInteger& operator=(const BigInteger& rhs)
{
cout << "copy assignment" << endl;
BigInteger tmp(rhs);
this->arr = tmp.arr;
return *this;
}
BigInteger& operator=(BigInteger&& rhs) noexcept
{
cout << "move assignment" << endl;
swap(*this, rhs);
return *this;
}
};
int main()
{
BigInteger another = BigInteger({0, 1, 2});
}
So, I'm creating a temporary object BigInteger({0, 1, 2}) and then doing the move assignment for my class instance a (theoretically). So expected output is:
initializer_list constructor //creating temporary object
default constructor //creating glvalue (non-temporary) object
move assignment
destructor
But the output is:
initializer_list constructor
destructor
And I don't even understand why is that happening. I suspect that operator= is not the same as initialization, but still I don't understand how my object is constructed.
Primarily, move assignment operator isn't called because you aren't assigning anything. Type name = ...; is initialisation, not assignment.
Furthermore, there isn't even move construction because BigInteger({0, 1, 2}) is a prvalue of the same type as the initialised object, and thus no temporary object will be materialised, but rather the the initialiser of the prvalue is used to initialise another directly, as if you had written BigInteger another = {0, 1, 2}; (which is incidentally what I recommend that you write; There's simply no need to repeat the type).
I suspect that operator= is not the same as initialization
This is correct. Assignment operator and initialisation are two separate things. You aren't using the assignment operator in this example.
So, I'm creating a temporary object BigInteger({0, 1, 2}) and then
doing the move assignment for my class instance a (theoretically).
Is seems you mean the move constructing instead of the move assignment because you are speaking about a declaration.
From the C++ 14 Standard (12.8 Copying and moving class objects)
31 When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the constructor
selected for the copy/move operation and/or the destructor for the
object have side effects. In such cases, the implementation treats the
source and target of the omitted copy/move operation as simply two
different ways of referring to the same object, and the destruction of
that object occurs at the later of the times when the two objects
would have been destroyed without the optimization.122 This elision of
copy/move operations, called copy elision, is permitted in the
following circumstances (which may be combined to eliminate multiple
copies):
(31.3) — when a temporary class object that has not been bound to a
reference (12.2) would be copied/moved to a class object with the same
cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move
So in this declaration
BigInteger another = BigInteger({0, 1, 2});
the move operation is omitted by the constructing the temporary object directly into the target of the omitted copy/move.
This question already has answers here:
Strange behavior of copy-initialization, doesn't call the copy-constructor!
(6 answers)
Copy constructor not calling
(2 answers)
Closed 2 years ago.
#include <iostream>
class Test {
public:
Test(const int& i)
{
std::cout << "Direct" << std::endl;
}
Test(const Test& t)
{
std::cout << "Copy" << std::endl;
}
};
int main()
{
Test test = 1;
return 0;
}
This program(compiled in C++11) will only output Direct, but the Test test = 1; means implicit convert 1 to a Test and then copy the result to test, I expected it output both of Direct and Copy, could anyone explain it?
Until c++17, this initialization:
Test test = 1;
will create a temporary Test from the int 1, and then the copy constructor is invoked for test. In practice, compilers will do copy-elision, and the temporary will be elided. You can force the compiler to not do the elision by passing the -fno-elide-constructors flag, to see both constructor calls.
From c++17, the wording is changed, and there is simply no temporary on the right hand side, so there is nothing to elide, and only one constructor is called. So even if you use -fno-elide-constructors, you will only see a single constructor call.
This question already has answers here:
What are copy elision and return value optimization?
(5 answers)
Closed 5 years ago.
I was testing my knowledge in C++ and have encountered a problem. Consider this code:
class A
{
public:
A(int n = 0)
: m_n(n)
{
std::cout << 'd';
}
A(const A& a)
: m_n(a.m_n)
{
std::cout << 'c';
}
A(A&& a){
std::cout<<'r';
}
private:
int m_n;
};
int main()
{
A a = A();
std::cout << std::endl;
return 0;
}
Clearly, the A() constructor is an rvalue, as no permanent object has been created. So I think that first I have to see "d" as output. Then we are copying the rvalue to our new object, which is yet to be initialised. I have implemented a copy constructor that accepts an rvalue as an argument but I did not see the proof (no "r" output).
Can someone explain why that is?
You're seeing a form of copy elision -- the compiler is actually required to optimize and implement A a = A() by initializing a directly with the arguments to the constructor expression (() in this case) rather than creating a temporary and copying it into a. If you want to enforce an unnamed object to be copied, you need to initialize a with an expression that is not a simple constructor call.
Note that even then, the compiler may elide construtor calls by the as-if rule (if the only visible side effects are in the copy/move constructors/destructors. Your best bet to "force" a call to the move ctor is to use a named value and std::move:
A a1;
A a2 = std::move(a1);