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.
Related
Consider the following program:
#include <vector>
#include <iostream>
class A {
int x;
public:
A(int n) noexcept : x(n) { std::cout << "ctor with value\n"; }
A(const A& other) noexcept : x(other.x) { std::cout << "copy ctor\n"; }
A(A&& other) noexcept : x(other.x) { std::cout << "move ctor\n"; }
~A() { std::cout << "dtor\n"; } // (*)
};
int main()
{
std::vector<A> v;
v.emplace_back(123);
v.emplace_back(456);
}
If I run the program, I get (GodBolt):
ctor with value
ctor with value
move ctor
dtor
dtor
dtor
... which is in line with what I would expect. However, if on line (*) I mark the destructor as potentially throwing, I then get :
ctor with value
ctor with value
copy ctor
dtor
dtor
dtor
... i.e. the copy ctor is used instead of the move ctor. Why is this the case? It doesn't seem copying prevents destructions that moving would necessitate.
Related questions:
are std::vector required to use move instead of copy?
How to enforce move semantics when a vector grows?
Vector reallocation uses copy instead of move constructor
This is LWG2116. The choice between moving and copying the elements is often expressed as std::is_nothrow_move_constructible, i.e. noexcept(T(T&&)), which also erroneously checks the destructor.
tl;dr: Because std::vector prefers to offer you a "strong exception guarantee".
(Thanks goes to Jonathan Wakely, #davidbak, #Caleth for links & explanations)
Suppose std::vector were to use move construction in your case; and suppose that an exception were to be thrown during vector-resizing, by one of the A::~A calls. In that case, you would have an unusable std::vector, partially moved.
On the other hand, if std::vector performs copy construction, and an exception occurs in one of the destructors - it can simply ditch the new copy, and your vector will be in the same state it was before the resizing. That is the "strong exception guarantee" for the std::vector object.
The standard library designers chose to prefer this guarantee over optimizing the performance of vector resizing.
This had been reported as an issue/defect with the standard library (LWG 2116) - but after some discussion, it was decided to keep the current behavior as per the above consideration.
See also Arthur O'Dwyr's post: A "Pick any two" triangle for std::vector.
See code below. I comment move constructor and instead of compilation error, copy constructor is now called! Despite the fact that I am using std::move.
How I can be ensure, that my huge object never call copy constructor (for example, I forgot to add move constructor) if I use std::move?
class MyHugeObject
{
public:
MyHugeObject()
{
}
MyHugeObject(const MyHugeObject& m)
{
std::cout << "copy huge object\n";
}
// MyHugeObject(MyHugeObject&& m)
// {
// std::cout << "move huge object\n";
// }
};
MyHugeObject func()
{
MyHugeObject m1;
MyHugeObject&& m2 = std::move(m1);
return std::move(m2);
}
int main()
{
auto m = func();
return 0;
}
Output:
copy huge object
If copy construction of your type is especially expensive or problematic, but not something you want to completely prohibit, you can mark the copy constructor explicit. An explicit copy constructor will not allow copying if not explicitly requested (e.g. you can’t use it to automatically pass arguments by value) but will still be available for explicit copy construction. Meanwhile, implicit move construction can still be allowed.
This is an educational question, I am interested in what happens behind the scenes when I do:
SomeClass x(arg1, arg2, arg3); // An instance of SomeClass is constructed.
x = SomeClass(arg4, arg5, arg6); // Intent is to create a new instance.
SomeClass does not have operator= implemented.
Does the space allocated to x simply get overwritten as if it was newly allocated memory or what exactly happens? And when is it a good idea?
This can best be explained with the help of a small example:
Live on Coliru
struct A {
A(int a) { cout << "A::ctor\n"; } //ctor
A(const A& a) { cout << "A::copy\n"; } //copy ctor
A& operator=(const A& a) { cout << "A::operator=\n"; } //copy assign
};
int main()
{
A a(2); //calls constructor
a = A(10); //calls constructor first, then copy assignment
}
Output:
A::ctor
A::ctor
A::operator
The above is pretty self explanatory. For the first, only the constructor gets called. For the second, first the constructor is called and then copy assignment.
SomeClass does not have operator= implemented.
That doesn't matter because the compiler can generate one for you. If you explicitly delete it, then the above code will not compile. However, if you have a move constructor defined then that will be used:
(I highly recommend you read The rule of three/five/zero and understand it. It is among the top 5 things in C++ that you should know.)
A& operator=(const A& a) = delete; //copy assign deleted
A& operator=(A&& other) { cout << "move assigned\n"; } //move assign available
Now you maybe wondering what will happen if both copy and move assign are available. Lets see:
A a(2); //ctor
a = A(10); //ctor + move assign
A b(3); //ctor
b = a; // copy assign only
a = std::move(b); // move assign
For a = A(10) move assign is invoked because A(10) is an rvalue of the same type as what is on the left hand side of the =.
For the last case a = std::move(b);, we explicitly cast b to an rvalue (yes that's what std::move() does). Since it's an rvalue now, move assignment is invoked.
Does the space allocated to x simply get overwritten as if it was newly allocated memory or what exactly happens?
First the temporary is created: A(10). Space will of course be allocated for it.
It's result is then assigned to a, so previous values in a get overwritten
destructor for the temporary will be called
And when is it a good idea?
It is a good idea when you need it, it depends on your usecase. Generally I would recommend that don't copy assign unnecessarily.
Second line is call of constructor followed by call to assignment operator. Assigment default to shallow copy of non-static members into existing storage.
If you defined something that prevented compiler to create default operator=, i.e. you defined move constructor or move assignment, no assignment is possible unless you declared your own (why it is so surprising?) If default shallow copy is fine, you can write following declaration:
SomeClass& operator(const SomeClass&) = default;
= default provides mechanism to declare "default" behavior of special functions.
Now there is move assignment and in such case one would be preferred if it declared in given context. But it won't be declared by compiler if user provided destructor or copy\move constructor\assignment operator.
SomeClass& operator(SomeClass&&) = default;
Difference between two assignments exists only for class-types where "move" semantics may include transfer of ownership. For trivial types and primitive types it's a simple copy.
Compiler allowed to elide some actions including creation of storage for temporary object, so resulting code may actually write new values directly into x storage, provided that such elision won't change program behavior.
I am creating a class of chaining-type, such as the small example below. It seems that when chaining member functions, then the copy constructor is invoked. Is there a way to get rid of the copy constructor call? In my toy example below, it is obvious that I'm only dealing with temporaries and thus there "should" (maybe not by the standards, but logically) be an elision. The second best choice, to copy elision, would be for the move constructor to be called, but this is not the case.
class test_class {
private:
int i = 5;
public:
test_class(int i) : i(i) {}
test_class(const test_class& t) {
i = t.i;
std::cout << "Copy constructor"<< std::endl;
}
test_class(test_class&& t) {
i = t.i;
std::cout << "Move constructor"<< std::endl;
}
auto& increment(){
i++;
return *this;
}
};
int main()
{
//test_class a{7};
//does not call copy constructor
auto b = test_class{7};
//calls copy constructor
auto b2 = test_class{7}.increment();
return 0;
}
Edit: Some clarifications.
1. This does not depend on optimization level.
2. In my real code, I have more complex (e.g. heap allocated) objects than ints
Partial answer (it doesn't construct b2 in place, but turns the copy construction into a move construction): You can overload the increment member function on the value category of the associated instance:
auto& increment() & {
i++;
return *this;
}
auto&& increment() && {
i++;
return std::move(*this);
}
This causes
auto b2 = test_class{7}.increment();
to move-construct b2 because test_class{7} is a temporary, and the && overload of test_class::increment is called.
For a true in-place construction (i.e. not even a move construction), you can turn all special and non-special member functions into constexpr versions. Then, you can do
constexpr auto b2 = test_class{7}.increment();
and you neither a move nor a copy construction to pay for. This is, obviously, possible for the simple test_class, but not for a more general scenario that doesn't allow for constexpr member functions.
Basically, assigning a reference to a value requires invoking a constructor, i.e. a copy or a move. This is different from copy-elision where it is known on both sides of the function to be the same distinct object. Also a reference can refer to a shared object much like a pointer.
The simplest way is probably to make the copy constructor being fully optimized away. The value setting is already optimized by the compiler, it is just the std::cout that cannot be optimized away.
test_class(const test_class& t) = default;
(or just remove both the copy and move constructor)
live example
Since your issue is basicly with the reference, a solution is probably not returning a reference to the object if you want to stop copying in this way.
void increment();
};
auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor
A third method is just relying on copy elision in the first place - however this requires a rewrite or encapsulation of the operation into one function and thus avoiding the issue altogether (I'm aware this may not be what you want, but could be a solution to other users):
auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called
A fourth method is using a move instead, either invoked explicit
auto b2 = std::move(test_class{7}.increment());
or as seen in this answer.
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)