I'm confused about when a move constructor gets called vs a copy constructor.
I've read the following sources:
Move constructor is not getting called in C++0x
Move semantics and rvalue references in C++11
msdn
All of these sources are either overcomplicated(I just want a simple example) or only show how to write a move constructor, but not how to call it. Ive written a simple problem to be more specific:
const class noConstruct{}NoConstruct;
class a
{
private:
int *Array;
public:
a();
a(noConstruct);
a(const a&);
a& operator=(const a&);
a(a&&);
a& operator=(a&&);
~a();
};
a::a()
{
Array=new int[5]{1,2,3,4,5};
}
a::a(noConstruct Parameter)
{
Array=nullptr;
}
a::a(const a& Old): Array(Old.Array)
{
}
a& a::operator=(const a&Old)
{
delete[] Array;
Array=new int[5];
for (int i=0;i!=5;i++)
{
Array[i]=Old.Array[i];
}
return *this;
}
a::a(a&&Old)
{
Array=Old.Array;
Old.Array=nullptr;
}
a& a::operator=(a&&Old)
{
Array=Old.Array;
Old.Array=nullptr;
return *this;
}
a::~a()
{
delete[] Array;
}
int main()
{
a A(NoConstruct),B(NoConstruct),C;
A=C;
B=C;
}
currently A,B,and C all have different pointer values. I would like A to have a new pointer, B to have C's old pointer, and C to have a null pointer.
somewhat off topic, but If one could suggest a documentation where i could learn about these new features in detail i would be grateful and would probably not need to ask many more questions.
A move constructor is called:
when an object initializer is std::move(something)
when an object initializer is std::forward<T>(something) and T is not an lvalue reference type (useful in template programming for "perfect forwarding")
when an object initializer is a temporary and the compiler doesn't eliminate the copy/move entirely
when returning a function-local class object by value and the compiler doesn't eliminate the copy/move entirely
when throwing a function-local class object and the compiler doesn't eliminate the copy/move entirely
This is not a complete list. Note that an "object initializer" can be a function argument, if the parameter has a class type (not reference).
a RetByValue() {
a obj;
return obj; // Might call move ctor, or no ctor.
}
void TakeByValue(a);
int main() {
a a1;
a a2 = a1; // copy ctor
a a3 = std::move(a1); // move ctor
TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.
a a4 = RetByValue(); // Might call move ctor, or no ctor.
a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}
First of all, your copy constructor is broken. Both the copied from and copied to objects will point to the same Array and will both try to delete[] it when they go out of scope, resulting in undefined behavior. To fix it, make a copy of the array.
a::a(const a& Old): Array(new int[5])
{
for( size_t i = 0; i < 5; ++i ) {
Array[i] = Old.Array[i];
}
}
Now, move assignment is not being performed as you want it to be, because both assignment statements are assigning from lvalues, instead of using rvalues. For moves to be performed, you must be moving from an rvalue, or it must be a context where an lvalue can be considered to be an rvalue (such as the return statement of a function).
To get the desired effect use std::move to create an rvalue reference.
A=C; // A will now contain a copy of C
B=std::move(C); // Calls the move assignment operator
Remember that copy elision could occur. If you disable it by passing the -fno-elide-constructors flag to the compiler your constructor might get executed.
You can read about it here: https://www.geeksforgeeks.org/copy-elision-in-c/
Answers above do not give a 'natural' example when a move constructor is called. I found this way to call move constructor without std::move (and without suppressing copy elision by -fno-elide-constructors):
a foo(a a0) {
return a0; // move ctor is called
}
a a1 = foo(a());
Related
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.
The rule-of-three is well known and says that if one has to define a destructor, so one has most probably to define a copy constructor and an assignment operator.
However, recently in some code, I stumbled upon a "rule-of-two": Only the destructor and the copy constructor were defined and the assign operator was left for compiler to define. My first thought was "this must be an error", but now I'm not that sure, because all compilers (gcc, msvs, intel) produced assignment operator which called the copy constructor.
Simplified, the class looks as follows:
struct A{
size_t size;
int *p;
A(size_t s): size(s), p(new int[size]){}
A(const A&a): size(a.size), p(new int[size]){
std::copy(a.p, a.p+a.size, p);
std::cout<<"copy constructor called\n";
}
~A(){
delete[] p;
}
};
And used like this:
int main(){
A a(2);
a.p[0]=42.0;
A b=a;
std::cout<<"first: "<<b.p[0]<<"\n";
}
produces the following output:
copy constructor called
first: 42
My question: It is guarantied, that the compiler defined assignment operator will call the copy constructor, or it is only a lucky coincidence, that all compilers do it that way?
Edit: It's true, I confused initialization and assignment! Replacing A b=a; by A b(0); b=aleads as expected to a double-free-error.
It's you who misunderstands what's happening, there is no assignment at all going on, only construction and initialization.
When you do
A b = a;
there's no assignment, only initialization. More precisely copy initialization. It's the same as writing
A b(a);
Initialization create a new object:
A a; // initialization
A b(a); // initialization
A c = a; // initialization
Assignment modifies an existing object:
A a;
a = 3; // assignment
In the code example, as various comments have said, there is no assignment being done; all of the examples are initializations, so the compiler-generated assignment operator is not used.
If I have a struct in which I did not provide any copy and move constructor:
struct MyStruct {
MyStruct() { // this is the only function
...
}
...
};
then if I do the following:
std::vector<MyStruct> vec;
...
vec.push_back(MyStruct());
instead of using std::move() like the followings:
vec.push_back(std::move(MyStruct()));
Will c++11 smartly do the move for my temporary variable? Or, how can I make sure it is a move instead of a copy?
In C++11 std::vector::push_back will use a move constructor if passed an rvalue (and a move constructor exists for the type), but you should also consider using std::vector::emplace_back in such situations; std::vector::emplace_back will construct the object in place rather than moving it.
Will c++11 smartly do the move for my temporary variable? Or, how can I make sure it is a move instead of a copy?
It depends. This
vec.push_back(MyStruct());
will bind to
std::vector<MyStruct>::push_back(MyStruct&&);
but whether the rvalue passed is moved or copied depends fully on whether MyStruct has a move copy constructor (likewise for move assignment).
It will make absolutely no difference if you call
vec.push_back(std::move(MyStruct()));
because MyStruct() is already an rvalue.
So it really depends on the details of MyStruct. There is simply not enough information in your question to know if your class has move constructor.
These are the conditions that must be met for a class to have an implicitly generated move constructor:
no user-declared copy constructors
no user-declared copy assignment operators
no user-declared move assignment operators
no user-declared destructors
Of course, you can always provide your own if any of these conditions are not met:
MyStruct(MyStruct&&) = default;
Because the MyStruct() will create an rvalue the T && overload will be called.
It's actually very easy to verify (demo):
#include <iostream>
struct A{ int x; };
void foo(A &&x){ std::cout<<"&&" <<std::endl; }
void foo(A &x){ std::cout<<"&" <<std::endl; }
int main() {
foo(A()); // prints &&
A a;
foo(a); // prints &
return 0;
}
To clarify: I didn't mention anything about move constructor because one can have an explicitly deleted move constructor and still the T && will be invoked.
E.g (demo):
#include <iostream>
struct A{ int x; A() = default; A(const A& ) = default; A(A&&) = delete; };
/* ^
no move ctor */
void foo(A &&x){ std::cout<<"&&" <<std::endl; }
void foo(A &x){ std::cout<<"&" <<std::endl; }
int main() {
foo(A()); //still prints &&
A a;
foo(a);
return 0;
}
As I said before this is because it's an rvalue...
Yes, it is going to use push_back( T&& value ), and move the value.
If the type is movable then it definitely will be. In general standard-complying compiler should always choose move semantics to copy semantics if such are available.
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.
Edit: sorry I used "assignment constructor" instead of "assignment operator" in my original post. Fixed now.
It turns out that the copy constructor is called instead of the assignment operator in the following code. Anyone can tell me the reason behind this? Thank you.
class A
{
int i;
public:
A(int ii) { i = ii; }
A(const A& a) { i = a.i; i++; }
A& operator=(const A& a) { i = a.i; i--; }
};
int main(void)
{
A a(4);
A b = a;
return 0;
}
A a(4);
A b = a;
None of them is assignment1. Both are initialization.
First one is called direct-initialization, and second one is called copy-initialization.
The difference between them is that first one would work even if the copy-constructor is inaccessible (i.e its either private or protected), and second one would NOT work if the copy-constructor is inaccessible.
Even though the second one requires the copy-constructor to be accessible, that doesn't mean that the copy-constructor will necessarily be called. The compiler is allowed to optimize this, and so can elide the call to copy-constructor altogether. An accessible copy-constructor is needed for semantic validation.
See these topics :
Is there a difference in C++ between copy initialization and direct initialization?
When should you use direct initialization and when copy initialization?
c++ copy initialization & direct initialization, the weird case
1. And there is nothing such a thing called "assignment constructor".
operator= is not an "assignment constructor", it is an "assignment operator".
When you initialize a variable in its definition (as in A b = a), it is by definition equivalent to calling the copy constructor. ie, A b(a); and A b = a; are exactly equivalent.