Rvalue references and constructors - c++

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)

Related

Move constructor vs Copy elision

Can someone please explain me one thing. From the one side the move constructor was designed to optimize the memory & processor usage by eliminating unnecessary copying an objects BUT from other side almost everywhere the move constructor is going to be used the compiler uses copy elision, disabling the usage of the move ctor? Isn't it irrational?
There are plenty of cases where the move constructor will still get called and copy elision is not being used:
// inserting existing objects into a container
MyObject myobject;
std::vector<MyObject> myvector;
myvector.push_back(std::move(myobject));
// inserting temporary objects into a container
myvector.push_back(MyObject());
// swapping
MyObject other;
std::swap(myobject, other);
// calling functions with existing objects
void foo(MyObject x);
foo(std::move(myobject));
... and many more.
The only instance where there is mandatory copy elision (since C++17) is when constructing values from the result of a function call or a constructor. In such cases, the compiler isn't even allowed to use the move constructor. For example:
MyObject bar() {
return MyObject();
}
void example() {
MyObject x = bar(); // copy elision here
MyObject y = MyObject(); // also here
}
In general, the purpose of copy elision is not to eliminate move construction alltogether, but to avoid unnecessary constructions when initializing variables from prvalues.
See cppreference on Copy Elision.
Here is a simple example where move is called. It is a toy example for which the rule of zero could have been relevant, but assume that there are also other members inside the class that require going with the rule of five.
class A {
std::string s;
public:
A(const char* s = ""): s(s) {}
~A() {}
A(const A& a): s(a.s) {
std::cout << "copy ctor" << std::endl;
}
A& operator=(const A& a) {
s = a.s;
std::cout << "copy assignment" << std::endl;
return *this;
}
A(A&& a): s(std::move(a.s)) {
std::cout << "move ctor" << std::endl;
}
A& operator=(A&& a) {
s = std::move(a.s);
std::cout << "move assignment" << std::endl;
return *this;
}
};
int main() {
A a;
a = "hi"; // move
// suppose we KNOW here that a is not needed anymore
A a2 = std::move(a); // move
a = "bye"; // move
}
Code: http://coliru.stacked-crooked.com/a/97d25c43e0edb00b
Because copy elision has limits, compiler must know the lifetime of an object to predict whether copy elision can be done.
For example:
std::vecter<MyObj> v;
v.push(MyObj()); // compiler has a higher chance to do the copy elision
but consider this:
MyObj my_obj;
v.push(my_obj)
// ...
// my_obj will never use
in this case, the compiler won't know that the my_obj will be never use, so the normal copy will performed. If efficiency matters, you have to use v.push(std::move(my_obj)); to explicit tell the compiler that "I will never use my_obj again"
move constructor was designed to optimize the memory & processor usage by eliminating unnecessary copying an object
That is not true. A move construction creates a new object to which the data of the old object is "moved" (in the worst case if all data of the source object is fully enclosed in it, it is as expensive as a regular copy) a move constructor has only benefited over a copy constructor if you have member variables that can be swapped like pointers or containers that support swapping (or if it holds resources that can't be copied)
So copy elision is always desired over a move ctor. But that does not mean that a move ctor does not have any use. In many cases, however, a move ctor is just syntactic sugar over swap and reset/empty/destruct (not exactly true, but closely).
Besides the swap case, a move ctor is also useful for things that are or should not be copyable and for which you don't want to use pointers. e.g. a std::uniqu_ptr should not be copyable because of the unique ownership, but you might want to pass the ownership while calling a function, so moving its resources is important.
You can see move sematic as a standardized process, so that copy elision if possible, and if it is not a fallback to move ctor.

RVO and deleted move constructor in C++14

I have been learning about (N)RVO for a last few days. As I read on cppreference in the copy elision article, for C++14 :
... the compilers are permitted, but not required to omit the copy- and move- (since C++11)construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. This is an optimization: even when it takes place and the copy-/move-constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
So, either copy or move constructor must be present and accessible. But in the code bellow:
#include <iostream>
class myClass
{
public:
myClass() { std::cout << "Constructor" << std::endl; }
~myClass() { std::cout << "Destructor" << std::endl; }
myClass(myClass const&) { std::cout << "COPY constructor" << std::endl;}
myClass(myClass &&) = delete;
};
myClass foo()
{
return myClass{};
}
int main()
{
myClass m = foo();
return 0;
}
I got the following error: test.cpp: In function 'myClass foo()':
test.cpp:15:17: error: use of deleted function 'myClass::myClass(myClass&&)'
return myClass{};. I get this error even if I don't call foo() from the main(). The same issue with NRVO.
Hence the move constructor is always required, isn't it? (while the copy is not, I checked it)
I do not understand where compiler needs a move constructor. My only guess is that it might be required for constructing a temporary variable, but it sounds doubtful. Do someone knows an answer?
About the compiler: I tried it on g++ and VS compilers, you can check it online: http://rextester.com/HFT30137.
P.S. I know that in C++17 standard the RVO is obligated. But the NRVO isn't, so I want to study out what is going on here to understand when I can use NRVO.
Cited from cppreference:
Deleted functions
If, instead of a function body, the special syntax = delete ; is used, the function is defined as deleted.
...
If the function is overloaded, overload resolution takes place first, and the program is only ill-formed if the deleted function was selected.
It's not the same thing if you explicitly define the move constructor to be deleted. Here because of the presence of this deleted move constructor, although the copy constructor can match, the move constructor is better, thus the move constructor is selected during overload resolution.
If you remove the explicit delete, then the copy constructor will be selected and your program will compile.

Difference between the move assignment operator and move constructor?

For some time this has been confusing me. And I've not been able to find a satisfactory answer thus far. The question is simple. When does a move assignment operator get called, and when does a move constructor operator get called?
The code examples on cppreference.com yield the following interesting results:
The move assignment operator:
a2 = std::move(a1); // move-assignment from xvalue
The move constructor:
A a2 = std::move(a1); // move-construct from xvalue
So has it do to with which is implemented? And if so which is executed if both are implemented? And why is there the possibility of creating a move assignment operator overload at all, if it's identical anyway.
A move constructor is executed only when you construct an object. A move assignment operator is executed on a previously constructed object. It is exactly the same scenario as in the copy case.
Foo foo = std::move(bar); // construction, invokes move constructor
foo = std::move(other); // assignment, invokes move assignment operator
If you don't declare them explicitly, the compiler generates them for you (with some exceptions, the list of which is too long to be posted here).
See this for a complete answer to when the move member functions are implicitly generated.
When does a move assignment operator get called
When you assign an rvalue to an object, as you do in your first example.
and when does a move constructor operator get called?
When you initialise an object using an rvalue, as you do in your second example. Although it isn't an operator.
So has it do to with which is implemented?
No, that determines whether it can be used, not when it might be used. For example, if there's no move constructor, then construction will use the copy constructor if that exists, and fail (with an error) otherwise.
And if so which is executed if both are implemented?
Assignment operator for assignment, constructor for initialisation.
And why is there the possibility of creating a move assignment operator overload at all, if it's identical anyway.
It isn't identical. It's invoked on an object that already exists; the constructor is invoked to initialise an object which previously didn't exist. They often have to do different things. For example, assignment might have to delete something, which won't exist during initialisation.
This is the same as normal copy assignment and copy construction.
A a2 = std::move(a1);
A a2 = a1;
Those call the move/copy constructor, because a2 doesn't yet exist and needs to be constructed. Assigning doesn't make sense. This form is called copy-initialization.
a2 = std::move(a1);
a2 = a1;
Those call the move/copy assignment operator, because a2 already exists, so it doesn't make sense to construct it.
Move constructor is called during:
initialization: T a = std::move(b); or T a(std::move(b));, where b is of type T;
function argument passing: f(std::move(a));, where a is of type T and f is void f(T t);
Move assignment operation is called during:
function return: return a; inside a function such as T f(), where a is of type T which has a move constructor.
assignment
The following example code illustrates this :
#include <iostream>
#include <utility>
#include <vector>
#include <string>
using namespace std;
class A {
public :
A() { cout << "constructor called" << endl;}
~A() { cout << "destructor called" << endl;}
A(A&&) {cout << "move constructor called"<< endl; return;}
A& operator=(A&&) {cout << "move assignment operator called"<< endl; return *this;}
};
A fun() {
A a; // 5. constructor called
return a; // 6. move assignment operator called
// 7. destructor called on this local a
}
void foo(A){
return;
}
int main()
{
A a; // 1. constructor called
A b; // 2. constructor called
A c{std::move(b)}; // 3. move constructor called
c = std::move(a); // 4. move assignment operator called
a = fun();
foo(std::move(c)); // 8. move constructor called
}
Output :
constructor called
constructor called
move constructor called
move assignment operator called
constructor called
move assignment operator called
destructor called
move constructor called
destructor called
destructor called
destructor called
destructor called

In C++'s rule of three, why does operator= not call copy ctor?

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.

C++ Copy Constructor

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.