I have a following code:
#include <iostream>
using namespace std;
struct A
{
A() {}
A(const A&) { cout << "copy const" << endl; }
A(A&) { cout << "copy non const" << endl; }
};
A f(A a)
{
return a;
}
int main() {
A a1 = f(A());
}
The A(A&) copy constructor is called. Why A(const A&) is not called since I pass a temporary object?
When I comment out the A(const A&) copy constructor the program does not compile.
What you are seeing is a mix of copy elision and an actual copy being made. Since f takes a by value, It needs to copy A() into a. The compiler sees this copy really isn't needed, so it elides that and instead directly constructs a so you don't see any call. In the body of f when you return a;, it needs to copy a into the return value. Since a is a lvalue, A(A&) is a better match than A(const A&) so you see the call to the non const copy constructor. Then a1 needs to be initialized from f's return value. Again copy elision comes into play again and instead of seeing a copy it just directly puts the return value into a1's storage.
So, elide, non-const copy, elide, which leaves the output with just copy non const
You get an error when you remove A(const A&) because even though those copies were elided, C++ still required there to be copy constructor until C++17.
If you compile with gcc or clang and use -fno-elide-constructors you can actually see those copies. You can see that in this live example. Note that I used -std=c++11 to turn off C++17's guaranteed copy elision
Related
I have some code that returns a class object by value and the copy constructor is being called more than I thought it would. This seems to be how the g++ compiler does things but I'm not sure why. Say I have this code:
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "constructor" << endl; }
A(const A& other) { cout << "copy constructor" << endl; }
~A() { cout << "destructor" << endl; }
};
A f()
{
return A();
}
int main()
{
cout << "before f()\n";
A a = f();
cout << "after f()\n";
return 0;
}
When compiled with constructor elision turned off
g++ test.cpp -fno-elide-constructors
it outputs this:
before f()
constructor
copy constructor
destructor
copy constructor
destructor
after f()
destructor
So the copy constructor is called twice. I can see why the copy constructor would be called when copying the return value of f() into the variable a but why again?
I've read about C++ compilers returning temporary objects from functions but I don't understand the point of that. Even with elision turned off it seems unnecessary to create a temporary object for every function return value.
Update
Just to be super clear here, I'm not surprised that the copy constructor is being called. I expect that since I turned off elision. What I'm confused about is why the copy constructor needs to be called twice. It seems like once should be enough to copy the result from f() into a.
Pre-C++17
In Pre-C++17 standard there was non-mandatory copy elison, so by using the -fno-elide-constructors flag you are disabling the return value optimization and some other optimizations where copies are elided.
This is what is happening in your program:
First due to the return statement return A(); an object is constructed using the default constructor A::A().
Next, a copy of that default constructed object is returned to the caller. This is done using the copy constructor A::A(const A&). Hence you get the first copy constructor call output.
Finally, due to the statement A a = f(); which is copy initialization, the object named a is created as a copy of that returned value using the copy constructor A::A(const A&). Hence you get the second copy constructor call output.
//--v----------------------->1st copy constructor called due to this(return by value)
A f()
{
//-------------vvv---------->default constructor called due to this
return A();
}
//--vvvvvvvvv--------------->2nd copy constructor called due to this(copy initialization)
A a = f();
C++17
In C++17(and onwards), due to mandatory copy elison, you will not get any copy constructor call output. Demo.
It's because pre C++17, compilers were not mandated to elide the copies. If you do turn on compiling this as C++17 or later, there will only be one constructor call - even if you use -fno-elide-constructors.
Demo
The instances in C++14 with -fno-elide-constructors are created like this:
A // 2:nd (1:st copy)
f()
{
return A();
// 1:st
}
A a = f();
// 3:rd (2:nd copy)
If you instead return {};, you can skip the first temporary instance and only get one copy:
A // 1:st
f()
{
return {};
// arg to ctor
}
A a = f();
// 2:nd (1:st copy)
Demo
I have some code that returns a class object by value and the copy constructor is being called more than I thought it would. This seems to be how the g++ compiler does things but I'm not sure why. Say I have this code:
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "constructor" << endl; }
A(const A& other) { cout << "copy constructor" << endl; }
~A() { cout << "destructor" << endl; }
};
A f()
{
return A();
}
int main()
{
cout << "before f()\n";
A a = f();
cout << "after f()\n";
return 0;
}
When compiled with constructor elision turned off
g++ test.cpp -fno-elide-constructors
it outputs this:
before f()
constructor
copy constructor
destructor
copy constructor
destructor
after f()
destructor
So the copy constructor is called twice. I can see why the copy constructor would be called when copying the return value of f() into the variable a but why again?
I've read about C++ compilers returning temporary objects from functions but I don't understand the point of that. Even with elision turned off it seems unnecessary to create a temporary object for every function return value.
Update
Just to be super clear here, I'm not surprised that the copy constructor is being called. I expect that since I turned off elision. What I'm confused about is why the copy constructor needs to be called twice. It seems like once should be enough to copy the result from f() into a.
Pre-C++17
In Pre-C++17 standard there was non-mandatory copy elison, so by using the -fno-elide-constructors flag you are disabling the return value optimization and some other optimizations where copies are elided.
This is what is happening in your program:
First due to the return statement return A(); an object is constructed using the default constructor A::A().
Next, a copy of that default constructed object is returned to the caller. This is done using the copy constructor A::A(const A&). Hence you get the first copy constructor call output.
Finally, due to the statement A a = f(); which is copy initialization, the object named a is created as a copy of that returned value using the copy constructor A::A(const A&). Hence you get the second copy constructor call output.
//--v----------------------->1st copy constructor called due to this(return by value)
A f()
{
//-------------vvv---------->default constructor called due to this
return A();
}
//--vvvvvvvvv--------------->2nd copy constructor called due to this(copy initialization)
A a = f();
C++17
In C++17(and onwards), due to mandatory copy elison, you will not get any copy constructor call output. Demo.
It's because pre C++17, compilers were not mandated to elide the copies. If you do turn on compiling this as C++17 or later, there will only be one constructor call - even if you use -fno-elide-constructors.
Demo
The instances in C++14 with -fno-elide-constructors are created like this:
A // 2:nd (1:st copy)
f()
{
return A();
// 1:st
}
A a = f();
// 3:rd (2:nd copy)
If you instead return {};, you can skip the first temporary instance and only get one copy:
A // 1:st
f()
{
return {};
// arg to ctor
}
A a = f();
// 2:nd (1:st copy)
Demo
I read the following article about rvalue references http://thbecker.net/articles/rvalue_references/section_01.html
But there are some things I did not understand.
This is the code i used:
#include <iostream>
template <typename T>
class PointerHolder
{
public:
// 1
explicit PointerHolder(T* t) : ptr(t)
{
std::cout << "default constructor" << std::endl;
}
// 2
PointerHolder(const PointerHolder& lhs) : ptr(new T(*(lhs.ptr)))
{
std::cout << "copy constructor (lvalue reference)" << std::endl;
}
// 3
PointerHolder(PointerHolder&& rhs) : ptr(rhs.ptr)
{
rhs.ptr = nullptr;
std::cout << "copy constructor (rvalue reference)" << std::endl;
}
// 4
PointerHolder& operator=(const PointerHolder& lhs)
{
std::cout << "copy operator (lvalue reference)" << std::endl;
delete ptr;
ptr = new T(*(lhs.ptr));
return *this;
}
// 5
PointerHolder& operator=(PointerHolder&& rhs)
{
std::cout << "copy operator (rvalue reference)" << std::endl;
std::swap(ptr, rhs.ptr);
return *this;
}
~PointerHolder()
{
delete ptr;
}
private:
T* ptr;
};
PointerHolder<int> getIntPtrHolder(int i)
{
auto returnValue = PointerHolder<int>(new int(i));
return returnValue;
}
If I comment constructors 2 and 3, the compiler says :
error: use of deleted function ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’
auto returnValue = PointerHolder<int>(new int(i));
^
../src/rvalue-references/move.cpp:4:7: note: ‘constexpr PointerHolder<int>::PointerHolder(const PointerHolder<int>&)’ is implicitly declared as deleted because ‘PointerHolder<int>’ declares a move constructor or move assignment operator
But If I uncomment any one of the two, it compiles and the execution yields the following :
default constructor
So these are my questions :
When the constructors 2 and 3 where commented, it tried to call the constructor 2. Why ? I would expect it to call the constructor 1, which is what it did when i uncommented them !
Regarding the error : I declared a "move" copy operator, which means that my object can be "move" copied from a rvalue reference. But why does it implicitly delete my normal copy constructor ? And if so why does it allow me to "undelete" it by defining it explicitly and use it ?
When the constructors 2 and 3 where commented, it tried to call the constructor 2. Why ?
Because your declaration initialises returnValue from a temporary object - that temporary needs to be movable or copyable, using a move or copy constructor. When you comment these out, and inhibit their implicit generation by declaring a move-assignment operator, they are not available, so the initialisation is not allowed.
The actual move or copy should be elided, which is why you just see "default constructor" when you uncomment them. But even when elided, the appropriate constructor must be available.
why does it implicitly delete my normal copy constructor ?
Usually, if your class has funky move semantics, then the default copy semantics will be wrong. For example, it might copy a pointer to an object which is only supposed to be pointed to by a single instance of your class; which might in turn lead to double deletion or other errors. (In fact, your move constructor does exactly this, since you forgot to nullify the argument's pointer).
It's safer to delete the copy functions, and leave you to implement them correctly if you need them, than to generate functions which will almost certainly cause errors.
And if so why does it allow me to "undelete" it by defining it explicitly and use it ?
Because you often want to implement copy semantics as well as move semantics.
Note that it's more conventional to call 3 a "move constructor" and 5 a "move-assignment operator", since they move rather than copy their argument.
Because you're deleting the copy constructor and the line
auto returnValue = PointerHolder<int>(new int(i));
isn't a real assignment, it invokes a copy constructor to build the object. One of the two copy constructors (either by reference or by rvalue) needs to be available in order to succeed in initializing the object from that temporary. If you comment those both out, no luck in doing that.
What happens if everything is available? Why aren't those called?
This is a mechanism called "copy elision", basically by the time everything would be properly available to "initialize" returnValue with a copy-constructor, the compiler's being a smartboy and realizing:
"oh, I could just initialize returnValue like this"
PointerHolder<int> returnValue(new int(i));
and this is exactly what happens when everything is available.
As for why the move constructor seems to overcome the implicit copy-constructor, I can't find a better explanation than this: https://stackoverflow.com/a/11255258/1938163
You need a copy or move constructor to construct your return value.
If you get rid of all copy/move constructors and all assignment operators (use default generated constructors/operators) the code will compile, but fail miserably due to multiple deletions of the member ptr.
If you keep the copy/move constructors and assignment operators you might not see any invocation of a constructor, due to copy elision (return value optimization).
If you disable copy elision (g++: -fno-elide-constructors) the code will fail again due to multiple deletions of the member ptr.
If you correct the move-constructor:
// 3
PointerHolder(PointerHolder&& rhs) : ptr(0)
{
std::swap(ptr, rhs.ptr);
std::cout << "copy constructor (rvalue reference)" << std::endl;
}
And compile with disabled copy elision the result might be:
default constructor
copy constructor (rvalue reference)
copy constructor (rvalue reference)
copy constructor (rvalue reference)
The following "minimal" example should show the use of rule of 3 (and a half).
#include <algorithm>
#include <iostream>
class C
{
std::string* str;
public:
C()
: str(new std::string("default constructed"))
{
std::cout << "std ctor called" << std::endl;
}
C(std::string* _str)
: str(_str)
{
std::cout << "string ctor called, "
<< "address:" << str << std::endl;
}
// copy ctor: does a hard copy of the string
C(const C& other)
: str(new std::string(*(other.str)))
{
std::cout << "copy ctor called" << std::endl;
}
friend void swap(C& c1, C& c2) {
using std::swap;
swap(c1.str, c2.str);
}
const C& operator=(C src) // rule of 3.5
{
using std::swap;
swap(*this, src);
std::cout << "operator= called" << std::endl;
return *this;
}
C get_new() {
return C(str);
}
void print_address() { std::cout << str << std::endl; }
};
int main()
{
C a, b;
a = b.get_new();
a.print_address();
return 0;
}
Compiled it like this (g++ version: 4.7.1):
g++ -Wall test.cpp -o test
Now, what should happen? I assumed that the line a = b.get_new(); would make a hard copy, i.e. allocate a new string. Reason: The operator=() takes its argument, as typical in this design pattern, per value, which invokes a copy ctor, which will make a deep copy. What really happened?
std ctor called
std ctor called
string ctor called, address:0x433d0b0
operator= called
0x433d0b0
The copy ctor was never being called, and thus, the copy was soft - both pointers were equal. Why is the copy ctor not being called?
The copies are being elided.
There's no copy because b.get_new(); is constructing its 'temporary' C object exactly in the location that ends up being the parameter for operator=. The compiler is able to manage this because everything is in a single translation unit so it has sufficient information to do such transformations.
You can eliminate construction elision in clang and gcc with the flag -fno-elide-constructors, and then the output will be like:
std ctor called
std ctor called
string ctor called, address:0x1b42070
copy ctor called
copy ctor called
operator= called
0x1b420f0
The first copy is eliminated by the Return Value Optimization. With RVO the function constructs the object that is eventually returned directly into the location where the return value should go.
I'm not sure that there's a special name for elision of the second copy. That's the copy from the return value of get_new() into the parameter for operator= ().
As I said before, eliding both copies together results in get_new() constructing its object directly into the space for the parameter to operator= ().
Note that both pointers being equal, as in:
std ctor called
std ctor called
string ctor called, address:0xc340d0
operator= called
0xc340d0
does not itself indicate an error, and this will not cause a double free; Because the copy was elided, there isn't an additional copy of that object retaining ownership over the allocated string, so there won't be an additional free.
However your code does contain an error unrelated to the rule of three: get_new() is passing a pointer to the object's own str member, and the explicit object it creates (at the line "string ctor called, address:0xc340d0" in the output) is taking ownership of the str object already managed by the original object (b). This means that b and the object created inside get_new() are both attempting to manage the same string and that will result in a double free (if the destructor were implemented).
To see this change the default constructor to display the str it creates:
C()
: str(new std::string("default constructed"))
{
std::cout << "std ctor called. Address: " << str << std::endl;
}
And now the output will be like:
std ctor called. Address: 0x1cdf010
std ctor called. Address: 0x1cdf070
string ctor called, address:0x1cdf070
operator= called
0x1cdf070
So there's no problem with the last two pointers printed being the same. The problem is with the second and third pointers being printed. Fixing get_new():
C get_new() {
return C(new std::string(*str));
}
changes the output to:
std ctor called. Address: 0xec3010
std ctor called. Address: 0xec3070
string ctor called, address:0xec30d0
operator= called
0xec30d0
and solves any potential problem with double frees.
C++ is allowed to optimize away copy construction in functions that are returning a class instance.
What happens in get_new is that the object freshly constructed from _str member is returned directly and it's then used as the source for the assignment. This is called "Return Value Optimization" (RVO).
Note that while the compiler is free to optimize away a copy construction still it's required to check that copy construction can be legally called. If for example instead of a member function you have a non-friend function returning and instance and the copy constructor is private then you would get a compiler error even if after making the function accessible the copy could end up optimized away.
It isn't exactly clear why you expect the copy ctor to be used. The get_new() function will not create a new copy of the C object when it returns the value. This is an optimization called Return Value Optimization, any C++ compiler implements it.
I have a question about this syntax regarding initialization.
Quoted from http://en.wikipedia.org/wiki/Copy_constructor
X a = X();
// valid given X(const X& copy_from_me) but not valid given X(X& copy_from_me)
// because the second wants a non-const X&
// to create a, the compiler first creates a temporary by invoking the default constructor
// of X, then uses the copy constructor to initialize a as a copy of that temporary.
// However, for some compilers both the first and the second actually work.
#include <iostream>
class Foo
{
public:
Foo()
{
std::cout << "Default Constructor called" << std::endl;
}
Foo(const Foo& other)
{
std::cout << "Copy constructor called" << std::endl;
}
Foo& operator=(const Foo& rhs)
{
std::cout << "Assignment operator called" << std::endl;
}
};
int main()
{
Foo b = Foo(); //case 1:default
Foo c = Foo(a); //case 2: copy constructor
}
Case 1:
Upon changing the parameter from const to non const in the copy constructor, case 1 won't compile as expected from wikipedia. However, when ran using the proper copy constructor, it only calls the default constructor. Why doesn't it also call the copy constructor? Is this an optimization done at compile-time?
Case 2:
The answer to case 1 will probably answer case 2 for me, but why does this only call the copy constructor once?
Foo b = Foo();
This form requires a valid matching copy constructor to exist, but the copy may be optimized away. The fact that it may be optimized away does not relax the requirement that the constructor exist though.
By making your copy constructor take a non-const reference, it no longer matches, since Foo() generates a temporary, and temporaries cannot bind to non-const references. When you make the parameter const reference(or scrap your copy c-tor and use the compiler generated copy c-tor), then it works, because temporaries can bind to const references.
X() is a temporary, so you can't bind it to a non-const reference (although MSVS has an extension that allows it).
1) Yes, it's a compiler optimization
2) Illegal, because a doesn't exist. But in principle, again, yes, a compiler optimization.