C++ Copy Constructor - c++

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.

Related

Can construction of temporaries be optimized out if the return value is not assigned? [duplicate]

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

Copy constructor call for temporary object

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

return const value prevent move semantics

I am a beginner in cpp so excuse me for this question.
I was reading that returning const val prevents move semantics.
therefore I dont understand why the following code is compiled and works normally. is it because only temporary object is being created? in what cases the move semantics cannot being done? thank you in advance!
#include <iostream>
using namespace std;
const string foo()
{
return string("hello");
}
int main()
{
string other = std::move(foo());
}
std::move is just a unconditional cast to rvalue. In your case the return value of std::move(foo()) was const std::string&&. And because move constructor does not take const argument, copy constructor was called instead.
struct C {
C() { std::cout << "constructor" << std::endl; }
C(const C& other) { std::cout << "copy constructor" << std::endl; }
C(C&& other) { std::cout << "move constructor" << std::endl; }
};
const C get() {
return C();
}
int main() {
C c(std::move(get()));
return 0;
}
I was reading that returning const val prevents move semantics. therefore I dont understand why the following code is compiled and works normally.
When move semantics are prevented by some mechanism, this doesn't necessarily mean that the code doesn't compile. Often, it compiles happily, but an expected move construction turns out to be a copy instead.
Example: a type has a user provided copy ctor, which disables compiler-generated move ctors. When we think we move-construct, we don't.
struct Test {
Test() = default;
Test(const Test&) {}
};
Test t1;
Test t2{std::move(t1)}; // Copies!
in what cases the move semantics cannot being done?
Coming to your example, something that is const-qualified can't be used to move-construct another object in any meaningful way. Move construction makes sense when resources can be easily transferred, but const-ness prevents that. Example: a type has compiler-generate move and copy constructors, but we can't move-construct from a const instance.
struct Test {
Test() = default;
};
const Test t1;
Test t2{std::move(t1)}; // Copies!
Besides, it doesn't make sense to move something that is returned by a function by value:
string other = std::move(foo());
When foo() returns by value, you can move-construct from it, unless the return type is const. Hence, to enable move-construction of other:
std::string foo();
string other = foo();
std::move doesn't actually move anything. It is just an "rvalue cast". You cast something to rvalue, and the move constructor / move assignment operator does the actual moving if possible. "If possible" part is the key. In your example the return value is already an rvalue, so std::move literally does nothing. You may even get warnings like "nothing is moved". That is because the move constructor of std::string takes an argument of type std::string&& not const std::string&&. Because of that, the copy constructor is called.

Rvalue references and constructors

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)

Why copy elision not working with std::move?

I use the code below to test copy elision:
class foo
{
public:
foo() {cout<<"ctor"<<endl;};
foo(const foo &rhs) {cout<<"copy ctor"<<endl;}
};
int g(foo a)
{
return 0;
}
int main()
{
foo a;
g(std::move(a));
return 0;
}
I expected only the default constructor would be called because the argument of g() is an rvalue and copy will be elided. But the result shows that both the default constructor and the copy constructor are called. Why?
And if I change the function call to g(foo()), the copy will be elided. What's the difference between the return types of foo() and std::move(a)? How can I make the compiler elide copy on an lvalue?
Copy elision for can only occur in a few specific situations, the most common of which is the copying of a temporary (the others are returning locals, and throwing/catching exceptions). There is no temporary being produced by your code, so no copy is elided.
The copy constructor is being called because foo does not have a move constructor (move constructors are not implicitly generated for classes with explicit copy constructors), and so std::move(a) matches the foo(const foo &rhs) constructor (which is used to construct the function argument).
A copy of an lvalue can be elided in the following situations (although there is no way to force a compiler to perform the elision):
foo fn() {
foo localAutomaticVariable;
return localAutomaticVariable; //Copy to construct return value may be elided
}
int main() {
try {
foo localVariable;
throw localVariable; //The copy to construct the exception may be elided
}
catch(...) {}
}
If you want to avoid copies when passing function arguments, you can use a move constructor which pilfers the resources of the objects given to it:
class bar {
public:
bar() {cout<<"ctor"<<endl;};
bar(const bar &rhs) {cout<<"copy ctor"<<endl;}
bar(bar &&rhs) {cout<<"move ctor"<<endl;}
};
void fn(bar a)
{
}
//Prints:
//"ctor"
//"move ctor"
int main()
{
bar b;
f(std::move(b));
}
Also, whenever copy elision is allowed but does not occur, the move constructor will be used if it is available.
You need to declare g as:
int g(foo && a) //accept argument as rvalue reference
{
return 0;
}
Now it can accept argument by rvalue-reference.
In your case, even though the expression std::move(a) produces rvalue, it doesn't bind to a parameter which accepts argument by value. The receiving end must be rvalue-reference as well.
In case of g(foo()), the copy-elision is performed by the compiler, which is an optimization. It is NOT a requirement by the language[until C++17]. You can disable this optimization if you want to : then g(foo()) and g(std::move(a)) will behave exactly same, as expected.
But if you change g as I suggested above, the call g(foo()) will not make a copy because it is a requirement by the language to not make copy with &&. It is not a compiler-optimization anymore.