I am trying to write a move constructor for a structure but I can't understand why I fail to call the move constructor of structure member:
#include <memory>
struct C
{
std::unique_ptr<int[]> mVector;
size_t mSize;
C() = default;
C(C &&temp)
: mVector(temp.mVector)
, mSize(temp.mSize)
{}
};
When I compile this I get:
gcc -c TempTest.cpp
TempTest.cpp: In constructor 'C::C(C&&)':
TempTest.cpp:9:23: error: use of deleted function 'std::unique_ptr<_Tp [], _Dp>::unique_ptr(const std::unique_ptr<_Tp [], _Dp>&) [with _Tp = int; _Dp = std::default_delete<int []>]'
9 | , mSize(temp.mSize)
| ^
In file included from c:/msys64/mingw64/include/c++/10.3.0/memory:83,
from TempTest.cpp:1:
c:/msys64/mingw64/include/c++/10.3.0/bits/unique_ptr.h:723:7: note: declared here
723 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
Because in contructor temp is a rvalue reference, it is non-const so temp.mVector should be non-const and should call the unique_ptr move constructor but instead it calls the copy constructor which is deleted. Any idea where is the error?
Why rvalue reference member would be const?
Don't assume that it's const. You should assume that unique_ptr(const unique_ptr&) is merely the best match, from the available constructors.
Because in constructor temp is a rvalue reference
Surprise! It is not an r-value reference.
The variable temp is bound to an r-value, when the constructor is called. And now that it's a named variable, it's no longer a "temporary". It has become an l-value.
Since you know that the value was an r-value when the constructor was called, you can safely move the members, converting them back to an r-value.
C(C &&temp)
: mVector(std::move(temp.mVector))
// ^^^^^^^^^ We know that temp CAME FROM an r-value,
// so it can safely be moved.
, mSize(temp.mSize)
{}
try to run the following code and everything become clear:
struct Test
{
Test(){}
Test(const Test& r)
{
std::cout << "i am using the copy constructor :) " << std::endl;
}
Test(Test&& r)
{
std::cout << "I require std::move to be selected as possible overload.." << std::endl;
}
};
int main()
{
Test first;
Test second(first);
Test third(std::move(second));
return 0;
}
std::move helps to select the right constructor overload by passing a r-value reference (Test&&) to the constructor.
In your case, the compiler is selecting the copy constructor even if your vector isn't const, just because it is considered the best match (an implicit cast to a const reference is preferred to an implicit cast to a r-value reference, since the second one, differently from the first, may modify the value) adding a std::move you can select the right constructor and solve your issue.
#include <memory>
struct C
{
std::unique_ptr<int[]> mVector;
size_t mSize;
C() = default;
C(C &&temp)
: mVector(std::move(temp.mVector))
, mSize(temp.mSize)
{}
};
Related
My confusion lies at line Cube c = a;. I think it should call both default constructor and copy constructor, but in fact it only calls copy constructor.
Isn't Cube c, just like Cube a, a new object that should invoke default constructor?
class Cube
{
public:
int length_;
Cube(){
length_ = 1;
cout<< "Default Constr"<< endl;
}
Cube(const Cube & obj){
length_ = obj.length_;
cout<< "Copy Constr"<< endl;
}
};
Cube foo(){
Cube c;
return c;
}
int main() {
Cube a; //invoke default constructor only
Cube c = a; //invoke copy constructor only
return 0;
}
As others have pointed out, what you have here is copy initialization, so there's no way a default constructor would be used.
There is a slightly different case in which copy initialization can (at least theoretically) involve an extra step. Consider code like this:
class foo {
public:
foo(int) {}
};
int main() {
foo f = 1;
}
In this case, (at least before C++17) there were theoretically supposed to be two separate constructors involved. First, a temporary was constructed, initialized with 1, then the copy constructor was called to initialize f from that temporary object.
In this case, most compilers will generate code that just directly initializes f from q, so it's equivalent to foo f{1};. The compiler is still required to respect the fact that a copy is needed though, so if you delete the copy constructor, compilation will fail:
class foo {
foo(foo const &)= delete;
public:
foo(int) {}
};
int main() {
foo f = 1;
}
Result (with gcc):
trash9.cpp: In function 'int main()':
trash9.cpp:8:17: error: use of deleted function 'foo::foo(const foo&)'
foo f = 1;
^
trash9.cpp:2:9: note: declared here
foo(foo const &) = delete;
^~~
trash9.cpp:4:9: note: after user-defined conversion: 'foo::foo(int)'
foo(int) {}
^~~
But changes in the rules starting with C++17 mean that now even that is allowed (so if I add -std=c++17 to the compilation above, it succeeds).
When you write:
Cube c = a;
This is actually not an assignment but a copy-initialization.
In your case, it would be the same as if you had written:
Cube c(a);
Only the copy-constructor is called.
Keep in mind that an object is constructed only once, therefore only one constructor.
The default constructor is called.
It is used to initialize the object.
Then the copy constructor is used to set the state of the new object to be the same with the initial one.
For the following code, I know that because I implement a copy constructor the default move is blocked by the compiler, what I don't understand is why the compiler choose the copy constructor, and not issuing an error?
Can a rvalue reference be the parameter for a reference function if no rvalue ref function exists?
If not, what do happens here?
#include <iostream>
using namespace std;
class A{
public:
A(){printf("constructor\n");}
A(const A& a){printf("copy constructor");}
};
int main()
{
A a;
A b = std::move(a);
return 1;
}
The output is:
constructor
copy constructor
Thanks
The reason is that rvalue expressions can be bound to function parameters that are const lvalue references.
Also note that const rvalue expressions cannot be bound to rvalue references:
class A{
public:
A(){printf("constructor\n");}
A(const A& a){printf("copy constructor");}
A(A&&) = default; // <-- has move ctor
};
int main()
{
const A a; // <-- const!
A b = std::move(a); // <-- copy not move!!!
return 1;
}
The code above still calls the copy constructor, even though there is a move constructor.
std::move will take your named value and turn it into an rvalue expression, nothing more.
And, just like for any other rvalue, if it can't be moved, it'll be copied.
An rvalue of type T can be bound to a const T& just fine.
class TestClass
{
public:
TestClass(){
cout<<"constructor"<<endl;
p = {1,2,3};
cout<<(unsigned int *)(this->p.data())<<endl;
}
TestClass(const TestClass& test): p(std::move(test.p))
{
cout <<"copy constructor"<<endl;
cout<<(unsigned int *)(this->p.data())<<endl;
}
TestClass(TestClass && test): p(std::move(test.p))
{
cout <<"move constructor"<<endl;
cout<<(unsigned int *)(this->p.data())<<endl;
}
private:
std::vector<int> p;
};
int main()
{
TestClass t{};
TestClass p{t};
TestClass s{std::move(p)};
return 0;
}
And the output is
constructor
0xb92bf0
copy constructor
0xb915b0
move constructor
0xb915b0
I am just wondering why the address below constructor is different from the one below copy constructor. From what I understand, even it is a copy constructor, but I used std::move to get a rvalue reference and vector's move constructor should be called, so they should be same object.
std::move just casts whatever is passed to it to an xvalue, so rvalue-references can bind to it and may steal its resources. Here:
TestClass(const TestClass& test): p(std::move(test.p))
std::move will produce an expression of type const std::vector<int> &&, which, as you can see, has a const qualifier. If you check the copy- and move-constructors of std::vector on [vector], you'll see that the move-constructor expects an expression of type std::vector<T> &&, and the copy-constructor expects a const std::vector<T> &:
vector(const vector& x);
vector(vector&&) noexcept;
Compare the result of std::move(test.p) to these two constructors. Because an rvalue-reference doesn't bind to types with const qualifiers (unless the rvalue-reference is const-qualified), then the move-constructor overload isn't a good candidate. The other candidate (copy-constructor) does accept a const-qualified type, and since xvalues have the same properties as rvalues:
http://en.cppreference.com/w/cpp/language/value_category#rvalue
An rvalue may be used to initialize a const lvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends.
, the copy-constructor is a good candidate and is selected.
When compiled with Clang 3.9.1 or GCC 6.3.0 throwing movable but not copyable objects seems to work fine:
struct MovableNonCopyable {
MovableNonCopyable();
~MovableNonCopyable();
MovableNonCopyable(MovableNonCopyable &&);
MovableNonCopyable(MovableNonCopyable const &) = delete;
MovableNonCopyable & operator=(MovableNonCopyable &&);
MovableNonCopyable & operator=(MovableNonCopyable const &) = delete;
};
void f() { throw MovableNonCopyable(); }
But throwing copyable but not movable objects like this:
struct CopyableNonMovable {
CopyableNonMovable();
~CopyableNonMovable();
CopyableNonMovable(CopyableNonMovable &&) = delete;
CopyableNonMovable(CopyableNonMovable const &);
CopyableNonMovable & operator=(CopyableNonMovable &&) = delete;
CopyableNonMovable & operator=(CopyableNonMovable const &);
};
void g() { throw CopyableNonMovable(); }
instead causes compilation error like:
test.cpp: In function 'void g()':
test.cpp:21:41: error: use of deleted function 'CopyableNonMovable::CopyableNonMovable(CopyableNonMovable&&)'
void g() { throw CopyableNonMovable(); }
^
test.cpp:15:9: note: declared here
CopyableNonMovable(CopyableNonMovable &&) = delete;
^~~~~~~~~~~~~~~~~~
Why is this? According to [except.throw#5] this should be the other way around, i.e. the copy constructor should be accessible.
Here, you are explicitly asking the compiler to prevent construction from rvalue objects.
When you throw your temporary CopyableNonMovable() object, the compiler looks for the appropriate constructor for its "copy" it has to throw. The declared constructor that fits best is the move constructor, since rvalues bind best to rvalue references. It looks at the declaration, sees it as deleted, and therefore has to refuse it.
The best solution is to simply not declare the move constructor, which will make it implicitly not generated, since a copy constructor was declared. In that case, the rvalues will bind best to the reference to const CopyableNonMoveable
I have coded a very basic class
class A
{
int n;
public:
A(int& val){val=n;}
A(const int& val=0){n=val;}
A(A& val){n=val.n;}//work without this constructor
};
int main()
{
A a=3;//want to call A::A(const int&)
return 0;
}
I don't want to create a constructor with a copy from an instance of A (for a future use)
What's wrong with this simple code?
Error message :
...\main.cpp||In function 'int main()':|
...\main.cpp|16|error: no matching function for call to 'A::A(A)'|
...\main.cpp|16|note: candidates are:|
...\main.cpp|11|note: A::A(A&)|
...\main.cpp|11|note: no known conversion for argument 1 from 'A' to 'A&'|
...\main.cpp|10|note: A::A(const int&)|
...\main.cpp|10|note: no known conversion for argument 1 from 'A' to 'const int&'|
...\main.cpp|9|note: A::A(int&)|
...\main.cpp|9|note: no known conversion for argument 1 from 'A' to 'int&'|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
It seems 3 is considered as an instance of A?
If I add A(const A& val){n=val.n;}
the constructor A(const int&) is chosen.
What can I do to compile successfully without A(const A& val)?
The issue is your copy constructor: A(A& val) { n=val.n; }.
Given this line A a = 3; one possibility is to use A a = A(3) which in turn will use the copy constructor. However, it is against the standard to bind a temporary to non-const. A proper copy constructor will solve the problem.
Example Code
#include <iostream>
class A
{
int n;
public:
A(int& val) : n(val) { std::cout << "A(int&)\n"; }
A(const int& val=0) : n(val) { std::cout << "A(const int&)\n"; }
A(const A& val) : n(val.n) { std::cout << "A(const A&)\n"; }
};
int main()
{
A a = 3;
return 0;
}
Example Output
A(const int&)
Live Example
Note:
The above code makes proper use of the initialization lists
The output shows the copy constructor is not actually invoked
The line of code;
A a = 3;
Is using copy initialisation to work. For that to work, a copy of A is needed and is duly made. You have two constructors taking an int as an argument (and neither are explicit).
The copy cannot bind to A(A& val) because normal references don't bind to temporary values.
Possible solutions, direct initialisation;
A a { 3 };
Add or change the copy constructor to a move constructor;
A(A&& val);
Add a const to the copy constructor (although this is not what you wanted, but it does work);
A(A const& val);
Note both clang and g++ reject the original code correctly, but VC++ accepts it.
In this statement
A a=3;//want to call A::A(const int&)
there is created a temporary object of type A using constructor
A(const int& val=0){n=val;}
However a temporary object can be bound to a constant reference. So the compiler need that the copy constructor
A( const A& val){n=val.n;}//work without this constructor
^^^^^
would be at least accessible (even if it will not be called due to the copy constructor elision).
But the class does not has this constructor and the compiler issues an error.
On the other hand when you comment this constructor then the compiler itself implicitly defines this constructor and the code is compiled.
Take into account that this constructor
A(int& val){val=n;}
^^^^^^
does not make sense.:)