Why is this deleted move constructor accepted by the compiler? - c++

In the following code example, I have no particular reason to delete the move constructor, but I don't understand why it compiles (x is an object that prints whether it is moved or copied).
class foo {
X x;
public:
foo()=default;
foo(const foo &)=default;
foo(foo &&)=delete;
};
int main()
{
vector<foo> v;
foo a;
v.push_back(a);
v.push_back(a);
}
The second time push_back is called on the std::vector, it relocates the already existing object, usually with a move operation. With the foo move constructor declared 'default', I can see this happen.
However, when the move constructor is explicitly deleted, I'd expect compilation to fail since it is still available for overload, but deleted.
It does compile and the vector reallocation uses the copy constructor.
What is happening here ?

std::vector::resize() will move if it can, and copy if it must. This is extremely useful, as not all copyable types are movable. Keep in mind in c++ that objects are value types, not reference types.

I think you're using the copy constructor here aren't you? foo(const foo &). Keep in mind in c++ that objects are value types, not reference types.

To move a into the vector you have to make it a r-value, that is, to use std::move(a) instead of just a.
I believe if you do that your code will error on the deleted move ctor.

Related

Implicit move vs copy operations and containment

I am struggling to understand implicit move operations when a class has a member whose move operations were not defined:
int main() {
struct A // no move: move = copy
{
A() = default;
A(const A&) {
cout << "A'copy-ctor\n";
};
A& operator=(const A&) {
cout << "A'copy-assign\n";
return *this;
}
};
struct B
{
B() = default;
A a; // does this make B non-moveable?
unique_ptr<int> upi;
// B(B&&) noexcept = default;
// B& operator=(B&&)noexcept = default;
};
A a;
A a2 = std::move(a); // ok use copy ctor instead of move one
a2 = std::move(a); // ok use copy assignment instead of move one
B b;
B b2 = std::move(b); // why this works?
b = std::move(b2); // and this works?
// b = b2; // error: copy deleted because of non-copyable member upi
cout << "\nDone!\n";
}
So what I see is A is a non-moveable class because of the definition of its copy control operations so it can only be copied and any attempt to move an object of this class, the corresponding copy operation is used instead.
Until here it is OK if i am correct. But B has a non-copy-able object upi which is a unique_ptr thus the copy operations are defined as deleted functions so we cannot copy objects of this class. But this class has a non-move-able object a thus i think that this class (B) is neither copy-able nor move-able. But why the initialization of b2 and the assignment of b works fine? What happens exactly?
B b2 = std::move(b); // ok?!
Why the line above invokes the copy constructor of class A and does it invoke move constructor of B?
Things get more worse for me: if I uncomment the lines of move operations in B, the initialization above will not compile complaining about referencing a deleted funtion, the same thing for the assignment!
Can anyone help me what happens exactly? I have googled and read in cppreference and many websites before posting the question here.
The output:
A'copy-ctor
A'copy-assign
A'copy-ctor
A'copy-assign
Done!
Keep in mind what it means to "move" data in C++ (assuming we follow the usual conventions). If you move object x to object y, then y receives all the data that was in x and x is... well, we don't care what x is as long as it is still valid for destruction. Often we think of x as losing all of its data, but that is not required. All that is required is that x is valid. If x ends up with the same data as y, we don't care.
Copying x to y causes y to receive all the data that was in x, and x is left in a valid state (assuming the copy operation follows conventions and is not buggy). Thus, copying counts as moving. The reason for defining move operations in addition to copy operations is not to permit something new, but to permit greater efficiency in some cases. Anything that can be copied can be moved unless you take steps to prevent moves.
So what I see is A is a non-moveable class because of the definition of its copy control operations so it can only be copied and any attempt to move an object of this class, the corresponding copy operation is used instead.
What I see is that A is a moveable class (despite the lack of move constructor and move assignment), because of the definition of its copy control operations. Any attempt to move an object of this class will fall back on the corresponding copy operation. If you want a class to be copyable but not movable, you need to delete the move operations, while retaining the copy ones. (Try it. Add A(A&&) = delete; to your definition of A.)
The B class has one member that can be moved or copied, and one member that can be moved but not copied. So B itself can be moved but not copied. When B is moved, the unique_ptr member will be moved as you expect, and the A member will be copied (the fallback for moving objects of type A).
Things get more worse for me: if I uncomment the lines of move operations in B, the initialization above will not compile complaining about referencing a deleted funtion, the same thing for the assignment!
Read the error message more closely. When I replicated this result, the "use of deleted function" error was followed by a note providing more details: the move constructor was deleted because "its exception-specification does not match the implicit exception-specification". Removing the noexcept keywords allowed the code to compile (using gcc 9.2 and 6.1).
Alternatively, you could add noexcept to the copy constructor and copy assignment of A (keeping noexcept on the move operations of B). This is one way to demonstrate that the default move operations of B use the copy operations of A.
Here is a summary of #JaMiT's excellent answer:
Class A is moveable via it's copy-constructor and copy-assignment operator, even though class A is not MoveConstructible and is not MoveAssignable. See the notes on cppreference.com's pages for MoveConstructible and MoveAssignable.
And thus class B is also moveable.
The language allows you to prevent moveability for class A by explicitly =delete'ing the move-constructor and move-assignment, even though class A is still copyable.
Is there a practical reason to have a copyable but not-moveable class? Someone asked just this question several years ago here. The answers and comments struggled to find any practical reason to want a copyable but not-moveable class.
std::move does not force object to be copied. It just returns &&-reference (which allows compiler to use move ctor/assign operator).
In cases 1,2 object is copied.
In 3,4 cases (i think) object is moved. But A is still copied because it cannot be moved.

How does a C++ compiler decide when to call a move constructor for std::vector or any object

I was reading std template library book and was confused with below details listed in STL Containers chapter.
Apparently, it specifies the STD::VECTOR Operations and the effect
Operation Effect
vector<Elem> c(c2) | Copy constructor; creates a new vector as a copy of c2 (all elements are copied)
vector<Elem> c = c2 | Copy constructor; creates a new vector as a copy of c2 (all elements are copied)
vector<Elem> c(rv) | Move constructor; creates a new vector, taking the contents of the rvalue rv (since C++11)
vector<Elem> c = rv | Move constructor; creates a new vector, taking the contents of the rvalue rv (since C++11)
Apparently, there is no difference in the syntax for both move and copy constructors, when exactly are they called?
Let say you have a function f that returns a vector by value:
std::vector<int> f();
The value returned by the function is an rvalue.
And then lets say you want to call this function to initialize a vector of yours:
std::vector<int> v = f();
Now the compiler knows the vector returned by f will not be used any more, it's a temporary object, therefore it doesn't make sense to copy this temporary object since it will just be destructed at once anyway. So the compiler decides to call the move-constructor instead.
The move constructor is called where it makes sense to call it, i.e. moving an object and using it thereafter does not make sense, as the original object has been modified (possibly), which is not desirable:
std::vector<int> a = { 1 };
std::vector<int> b = a; //Let's say this called move constructor
int value = a[0]; //value is possibly not 1, the value may have changed due to the move
So, in that case, the copy constructor is called:
std::vector<int> a = { 1, 2 };
std::vector<int> b = a; //Copy constructor
But, here it does call the move constructor, because it is assigned to an rvalue, or a temporary value:
void foo(std::vector<int>) {}
foo({ 1, 2 }); //move constructor
The vector { 1, 2 } is a temporary vector, who cares if it has been modified? You will never know, as the vector will get destructed as soon as foo has ended. Copying the temporary vector would just be a waste of time.
Apparently , there is no difference in the syntax for both move and copy constructors
Indeed, this is true. This is not specific to std::vector either, but holds in general for all types. Copy-initialization by copy and by move have exactly the same syntax. Same goes for copy-assignment.
The difference comes from the type of argument expression. When the argument is a non-const r-value, the move constructor/assignment is preferred by overload resolution. Otherwise, move-constructor is not applicable, so copy constructor is used.
i assume rv is just a constructed object like c2
rv and c2 appear to not be objects. They are apparently placeholders for expressions. The book could be clearer about that (maybe it is, the excerpt is out of context after all).
Perhaps you should decouple syntax from semantics for better understanding. Let me make an analogy, consider this code.
struct A {
A(int i) {}
A(const std::string& s) {}
};
Now, if you find the following line, which constructor will be called?
A a(x);
You can't tell it because you don't know whether x has type int or std::string. It's not much different in your example, but with move semantics the compiler will inspect whether the argument is an rvalue or not. If such an A class provides a (move) constructor overload and x is an rvalue reference, then it will be preferred.
syntax of copy constructor:
classname (const classname&)
syntax of move constructor:
classname (classname&&)
The calling looks same together but their declarations are different.
Ref:
http://en.cppreference.com/w/cpp/language/copy_constructor
http://en.cppreference.com/w/cpp/language/move_constructor

Deleted copy constructor of tuple member cause error

This piece of code
#include <tuple>
struct Foo
{
Foo(const int& value):value_(value){}
//Foo(const Foo&)=delete; // delete copy constructor
int value_;
};
int main()
{
std::tuple<Foo> tup(std::move(Foo(1)));
return 0;
}
works fine but if you delete the Foo copy constructor it fails with the following compile error: use of deleted function Foo::Foo(const Foo&).
But, since I am telling explicitly that the object can be moved, why the std::tuple constructor uses the Foo copy constructor instead of its moving constructor? How can I enforce the std::tuple to be constructed moving the Foo instance instead of copying it?
But, since I am telling explicitly that the object can be moved,
No, you are telling the compiler that you want the object to be moved. That's not the same thing.
why the std::tuple constructor uses the Foo copy constructor instead of its moving constructor?
Because when you delete the copy constructor the implicit move constructor does not exist, so it can't be used.
How can I enforce the std::tuple to be constructed moving the Foo instance instead of copying it?
Either don't delete the copy constructor, or define a move constructor as well:
struct Foo
{
Foo(const int& value):value_(value){}
Foo(const Foo&)=delete; // delete copy constructor
Foo(Foo&&)=default;
int value_;
};
N.B. the std::move here is completely useless:
std::tuple<Foo> tup(std::move(Foo(1)));
All std::move does is cast its argument to an rvalue, but the temporary Foo(1) is already an rvalue, so you are casting an rvalue to an rvalue: useless. Furthermore, without the std::move the compiler can do copy elision and optimise away the actual move, but when you use std::move it can't do that and you make the code slower not faster!
The optimum code is the simplest version:
std::tuple<Foo> tup(Foo(1));
Or even simpler:
std::tuple<Foo> tup(1);

Error while declaring a class with std::vector of structs containing std::unique_ptr

Although having worked several years with C#, getting things done in C++ is sometime still difficult for me. I fully embrace the usage of smart pointers, but now I'm facing the following puzzle
I have a struct Foo, e.g.
struct Foo
{
Foo(std::unique_ptr<Bar> bar) : m_myBar(std::move(bar)) {}
private:
std::unique_ptr<Bar> m_myBar;
};
In a different class, I want to have a vector containing instances of Foo, but the following line
std::vector<Foo> m_Foos;
yields compile errors saying that the copy constructor is deleted. In the SO thread "Why can I not push_back a unique_ptr into a vector?" an explanation as well as a remedy is given. However, there the question concerns a vector of unique pointers whereas I have a vector of structs containing a unique pointer. The suggested solution is to use move semantics, but how does that apply in my situation? Or should I be doing something else?
As you say, m_Foos is actually a data member of another class (I'll call it FooHolder). If you didn't provide a copy constructor for FooHolder, the compiler will generate one automatically. That copy constructor will call the copy constructors of all data members of FooHolder, including m_Foos. Of course, the copy constructor of std::vector<Foo> contains a compilation error, since Foo is not copyable. This is probably why you're getting the error.
You'll have to provide an appropriate copy constructor for FooHolder, if you are able to and want that class to be copyable. Otherwise, you can just declare a move constructor (possibly defaulted), which will make the copy constructor deleted:
struct FooHolder
{
FooHolder(FooHolder&&) = default;
private:
std::vector<Foo> m_Foos;
};
You cannot copy unique pointers. You can only move them:
Foo(std::unique_ptr<Bar> bar) : m_myBar(std::move(bar)) {}
// ^^^^^^^^^^^^^^

Can a copy-constructor take a non-const parameter?

I have this problem, there is a function foo() as follows,
vector<ClassA> vec;
void foo()
{
ClassA a; //inside foo, a ClassA object will be created
a._ptr = new char[10];
vec.push_back(a); //and this newly created ClassA object should be put into vec for later use
}
And AFAIK, vec will invoke ClassA's copy-ctor to make a copy of the newly created object a, and here is the problem. If I define ClassA's copy-ctor the usual way,
ClassA::ClassA(const ClassA &ra) : _ptr(0)
{
_ptr = ra._ptr;
}
then object a and its copy (created by vec) will have pointers _ptr pointing to the same area, when foo finishes, a will call the destructor to release _ptr, then a's copy in vec will be a dangling pointer, right? Due to this problem, I want to implement ClassA's copy-ctor this way,
ClassA::ClassA(ClassA &ra) : _ptr(0) //take non-const reference as parameter
{
std::swap(_ptr, a._ptr);
}
Is my implementation ok? Or any other way can help accomplish the job?
To answer your titular question: Yes, any constructor for a class T that has one mandatory argument of type T & or T const & (it may also have further, defaulted arguments) is a copy constructor. In C++11, there's also a move constructor which requires one argument of type T &&.
Having a non-constant copy constructor that actually mutates the argument gives your class very unusual semantics (usually "transfer semantics") and should be extensively documented; it also prevents you from copying something constant (obviously). The old std::auto_ptr<T> does exactly that.
If at all possible, the new C++11-style mutable rvalue references and move constructors provide a far better solution for the problem of "moving" resources around when they're no longer needed in the original object. This is because an rvalue reference is a reference to a mutable object, but it can only bind to "safe" expressions such as temporaries or things that you have explicitly cast (via std::move) and thus marked as disposable.
C++11 introduced move constructors for this exact purpose:
ClassA::ClassA(ClassA&& ra)
: _ptr(ra._ptr)
{
ra._ptr = nullptr;
}
Alternatively you can declare _ptr to be a shared pointer:
std::shared_ptr<char[]> _ptr;
and then default denerated copy constructor will do just fine.
You should not copy the pointer, you should copy the context that the pointer is pointing to. You should also initialize the class by telling it how many elements you want, instead of allocating it by accessing a public pointer.
Since you want to copy the object, not move it, you should allocate resources in the new object when copying.
class A {
int* p_;
int size_;
public:
A(int size)
: p_(new int[size]()),
size_(size) {
}
A(const A &a)
: p_(new int[a.size_]),
size_(a.size_) {
std::copy(a.p_, a.p_ + a.size_, p_);
}
...
};
int main () {
A a(10);
vec.push_back(a);
}
However, if you know that the object you will copy isn't used after it's copied, you could move it's resources instead.
The problem with your implementation is that you will not be able to pass temporary objects as arguments for this copy-ctor (temporaries are always const). Like already mentioned the best solution would be to move to c++11 and use move semantics. If it is not possible shared_array can be an alternative.
Additional comment:
Avoid these kind of problems creating the object with new and storing pointers to the object in the vector.