Compiler tries to use copy constructor even though I'm moving - c++

I'm trying to move things in and out from my thead safe deque:
template <typename T>
class ThreadSafeDeque
{
//..
T pop_front(void) noexcept
{
std::unique_lock<std::mutex> lock{_mutex};
while (_collection.empty())
{
_condNewData.wait(lock);
}
auto elem = std::move(_collection.front());
_collection.pop_front();
return elem;
}
private:
std::deque<T> _collection; // Concrete, not thread safe, storage.
//...
}
I created this class to insert into the Deque:
class DecodedFrame
{
public:
DecodedFrame(){}
DecodedFrame(const DecodedFrame &decodedFrame) = delete;
DecodedFrame &operator=(const DecodedFrame &) = delete;
std::unique_ptr<AVFrame, AVFrameDeleter> avFrame;
Now I'm trying to do
std::shared_ptr<ThreadSafeDeque<DecodedFrame>> decodedFramesFifo;
//add some `DecodedFrame`s to decodedFramesFifo
DecodedFrame decodedFrame = std::move(decodedFramesFifo->pop_front());
But the compiler complains that I deleted the copy assignment constructor, even though I'm trying to use the move assingment constructor. My guess is that it happens because pop_front returns T, not T&. However, returning references makes no sense becaue the object is supposed to leave the deque forever and therefore the reference to it will die.
How can I move things here?
ps: how is it possible for the compiler to copy things when the DecodedFrame holds an unique_ptr? It can't be copied!

The problem is you declared your copy c'tor and assignment operator. Doesn't matter that the declaration deletes them, it's still a user supplied declaration. That suppresses the implicit declaration of the move operations. Your options are to
Default the move operations explicitly.
Remove the copy operation declarations, they will still be deleted implicitly on account of the noncopyable member.

The copy-ctor/assign operations are deleted (these are also declarations) but that does not implicitly declare/define move-ctor/assign operations.
See p30 of https://fr.slideshare.net/ripplelabs/howard-hinnant-accu2014
You have to declare (default) them.
DecodedFrame(DecodedFrame &&) = default;
DecodedFrame &operator=(DecodedFrame &&) = default;
In order to avoid such frustrating behaviour, you should consider the rule of five.
(https://en.cppreference.com/w/cpp/language/rule_of_three#Rule_of_five)

You dont get the move constructor and move assignment operator because your copy constructor and copy assignment operator are user delcared/defined (you deleted them). you can force the default move constructor and move assignment via "=default" (like you did with delete).
But because the class has a unique pointer as member which itself is only move constructible and move assignable, you will get the deletion of the copy constructor and copy assignment for free. just remove you delete statements and you will be fine, since you then again get the move operations.

Related

Which STD container to use to get [] operator and erase elements without moving memory around?

I used std::vector<MyClass> and everything was fine until I had to use erase() function. Problem caused by MyClass having
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
This is needed because MyClass holds unique_ptr objects and I generally want to avoid copy. But at this point my code has index access [] operator utilized, and I dont want to re write chucks of code. Is there a container in STD that is suitable?
Edit:
To people suggesting usage of move constructor, I've tried this:
//MyClass(const MyClass&) = delete;
//MyClass& operator=(const MyClass&) = delete;
MyClass(MyClass&&) = default;
but compiler(g++) still gives error:
/usr/include/c++/11/bits/stl_algobase.h:405:25: error: use of deleted function ‘M::MyClass& M::MyClass::operator=(const M::MyClass&)’
405 | *__result = std::move(*__first);
| ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
saying:
note: ‘M::MyClass& M::MyClass::operator=(const M::MyClass&)’ is implicitly declared as deleted because ‘M::MyClass’ declares a move constructor or move assignment operator
or I should not use default keyword and actually define move constructor?
As std::unique_ptr<T> is a moveable and non-copyable type, any class with a std::unique_ptr<T> member will by default be moveable and non-copyable (as long as all of the other members are at least moveable).
#include <memory>
#include <utility>
struct A {
std::unique_ptr<int> ptr;
};
int main()
{
A a;
//A b = a; //Will not compile
A c = std::move(a); // Perfectly legal
}
Therefore if you remove the copy contstructor, copy assignment operator, move constructor, and move assignment operator, then everything would work fine. Your type would not be copyable, and erasing from std::vector would work fine.
Note: std::vector<T>::operator[] returns a reference to the element so no copy is performed at this step either (for T not equal to bool).
If you want to explicitly = delete the copy semantics then you also have to = default both the move constructor and move assignment operator to keep the move semantics of the type. This is known as the rule of 5.

Why does using the move variant of std::vector::push_back invoke the moved item's copy constructor?

Here's some code that won't compile because push_back is trying to call the copy constructor in MoveOnlyClass, which is deleted:
class MoveOnlyClass
{
public:
MoveOnlyClass() {};
MoveOnlyClass& operator=(const MoveOnlyClass& other) = delete;
MoveOnlyClass(const MoveOnlyClass& other) = delete;
};
int main()
{
std::vector<MoveOnlyClass> vec;
vec.push_back(std::move(MoveOnlyClass()));
}
Why does this happen? Surely the only thing the vector should be calling is the move constructor. What would be the correct way to move an object into a vector?
Deleting the copy constructor/copy assignment function also implicitly deletes the move-constructor/move assignment function. If you intend to make an object movable but not copyable, you also need to default the move-constructor.
class MoveOnlyClass
{
public:
MoveOnlyClass() {};
MoveOnlyClass& operator=(const MoveOnlyClass& other) = delete;
MoveOnlyClass(const MoveOnlyClass& other) = delete;
MoveOnlyClass& operator=(MoveOnlyClass&& other) = default;
MoveOnlyClass(MoveOnlyClass&& other) = default;
};
//Will now compile as you expect
int main()
{
std::vector<MoveOnlyClass> vec;
vec.push_back(std::move(MoveOnlyClass()));
}
Also, std::move(T()) is redundant; constructing an object in-place like that will already make it an R-value, and using std::move when you don't need to may prevent some kinds of compiler optimizations (like Copy Ellision).
The answer by #Xirema seals the deal about the problem in the code, and explains why that is the case.
I just want to back it up with the appropriate excerpts from the language spec, to make things official. So from [class.copy.ctor¶8]:
(8) If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly declared as defaulted if and only if
(8.1) X does not have a user-declared copy constructor,
For this we should add that declaring as deleted is still declaring. Hence according to this we don't get the implicitly declared move constructor in your case, as we already have a user-declared copy constructor.
Further, under [dcl.fct.def.delete¶3]:
One can make a class uncopyable, i.e., move-only, by using deleted
definitions of the copy constructor and copy assignment operator, and
then providing defaulted definitions of the move constructor and move
assignment operator.

How does the move constructor look like if I have a vector (or anything like it) member variable?

The title pretty much sums up my question. In more detail: I know that when I declare a move constructor and a move assignment operator in C++11 I have to "make the other objects variables zero". But how does that work, when my variable is not an array or a simple int or double value, but its a more "complex" type?
In this example I have a Shoplist class with a vector member variable. Do I have to invoke the destructor of the vector class in the move assignment operator and constructor? Or what?
class Shoplist {
public:
Shoplist() :slist(0) {};
Shoplist(const Shoplist& other) :slist(other.slist) {};
Shoplist(Shoplist&& other) :slist(0) {
slist = other.slist;
other.slist.~vector();
}
Shoplist& operator=(const Shoplist& other);
Shoplist& operator=(Shoplist&& other);
~Shoplist() {};
private:
vector<Item> slist;
};
Shoplist& Shoplist::operator=(const Shoplist& other)
{
slist = other.slist;
return *this;
}
Shoplist& Shoplist::operator=(Shoplist&& other)
{
slist = other.slist;
other.slist.~vector();
return *this;
}
Whatever a std::vector needs to do in order to move correctly, will be handled by its own move constructor.
So, assuming you want to move the member, just use that directly:
Shoplist(Shoplist&& other)
: slist(std::move(other.slist))
{}
and
Shoplist& Shoplist::operator=(Shoplist&& other)
{
slist = std::move(other.slist);
return *this;
}
In this case, you could as AndyG points out, just use = default to have the compiler generate exactly the same move ctor and move assignment operator for you.
Note that explicitly destroying the original as you did is definitely absolutely wrong. The other member will be destroyed again when other goes out of scope.
Edit: I did say assuming you want to move the member, because in some cases you might not.
Generally you want to move data members like this if they're logically part of the class, and much cheaper to move than copy. While std::vector is definitely cheaper to move than to copy, if it holds some transient cache or temporary value that isn't logically part of the object's identity or value, you might reasonably choose to discard it.
Implementing copy/move/destructor operations doesn't make sense unless your class is managing a resource. By managing a resource I mean be directly responsible for it's lifetime: explicit creation and destruction. The rule of 0 and The rule of 3/5 stem from this simple ideea.
You might say that your class is managing the slist, but that would be wrong in this context: the std::vector class is directly (and correctly) managing the resources associated with it. If you let our class have implicit cpy/mv ctos/assignment and dtors, they will correctly invoke the corresponding std::vector operations. So you absolutely don't need to explicitly define them. In your case the rule of 0 applies.
I know that when I declare a move constructor and a move assignment
operator in C++11 I have to "make the other objects variables zero"
Well no, not really. The ideea is that when you move from an object (read: move it's resource from an object) then you have to make sure that your object it's left aware that the resource it had is no more under it's ownership (so that, for instance, it doesn't try to release it in it's destructor). In the case of std::vector, it's move ctor would set the pointer it has to the internal buffer to nullptr.
I know that when I declare a move constructor and a move assignment operator in C++11 I have to "make the other objects variables zero"
This is not quite correct. What you must do, is maintain validity of the moved from object. This means that you must satisfy the class invariant.
If you have specified a special invariant for a particular class, that requires you to set member variables to zero, then perhaps such class might have to do so. But this is not a requirement for move in general.
Do I have to invoke the destructor of the vector class in the move assignment operator and constructor?
Definitely not. The destructors of the members will be called when the moved from object is destroyed.
What you would typically do, is move construct/assign each member in the move constructor/assignment operator of the containing object. This is what the implicitly generated special member functions do. Of course, this might not satisfy the class invariant for all classes, and if it doesn't, then you may need to write your own versions of them.
The compiler will implicitly generate the special member functions for you, if you don't try to declare them yourself. Here is a minimal, but correct version of your class:
class Shoplist {
vector<Item> slist;
};
This class is default constructible, movable and copyable.
The move constructor should move member-wise:
Shoplist(Shoplist&& other)
: slist(std::move(other.slist))
{}
Note, that the compiler generates move constructors for you (when possible) by member-wise move, as you would do by hand above.
Move constructors are allowed (but not required) "steal" the contents of the moved-from object. This does not mean that they must "make the other objects variables zero". Moving a primitive type, for instance, is equivalent to copying it. What it does mean is that a move constructor can transfer ownership of data in the heap or free store. In this case, the moved-from object must be modified so that when it is destroyed (which should not happen in the move-constructor), the data it previously owned (before it was transferred) will not be freed.
Vector provides its own move constructor. So all you need to do in order to write a correct move constructor for an object containing a vector is to ensure the correct vector constructor is invoked. This is done by explicitly passing an r-value reference to the sub-object constructor, using std::move:
Shoplist(Shoplist&& other) :slist(std::move(other.slist)) {
//... Constructor body
... But in fact you probably don't need to do this in general. Your copy and move constructors will be correctly auto-generated if you don't declare them and don't declare a destructor. (Following this practice is called the "rule of 0".)
Alternatively, you can force the compiler to auto-generate the move constructor:
Shoplist(Shoplist&& other) = default;

Why does push_back succeed on a struct containing a unique_ptr unless that struct has a custom destructor?

The following code compiles if and only if I remove Foo's custom destructor.
struct Foo {
std::unique_ptr <int> bar;
~Foo (void) {} // This Line
};
std::vector <Foo> foos;
foos.push_back (Foo ());
Here is what I think I understand about the situation:
It fails because unique_ptrs cannot be copied, and std::vector::push_back (thing) calls the thing's copy constructor. If I write Foo a custom copy constructor which explicitly moves bar, then everything will be fine.
However, disabling This Line will cause the code to compile.
I thought that this should fail to compile even without This Line, because I'm still attempting to push_back a unique_ptr.
Why does this succeed without the custom destructor, and why does adding the custom destructor cause it to fail?
Edit: using gcc -std=gnu++11 on Debian Linux 64-bit
I can't guarantee this is what is happening in your case, but it is related to something I've seen lately:
You can't copy unique pointers, but you can move them.
In C++11 you will get a default move constructor if you don't define a destructor, but if you define one the compiler doesn't have to provide the move constructor, in which case the code fails. (I know that Visual Studio won't do this, but on my Mac with Xcode I've still gotten the move constructor.)
So, I think this is what is happening in your case. Try provide the destructor and the other constructors/assignment operators to see if it fixes things. (See the discussions on the rule of five.)
According to the standard "12.8.9 Copying and moving class objects [class.copy]"
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if:
X does not have a user-declared copy constructor,
X does not have a user-declared copy assignment operator,
X does not have a user-declared move assignment operator,
X does not have a user-declared destructor, and
the move constructor would not be implicitly defined as deleted.
So if a class doesn't have user-defined destructor a move-constructor will be declared. And according "12.8.15" of the standard, this will call move-constructor for unique_ptr class member:
The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.
If a class has user-defined destructor, the move-constructor should be declared explicitly:
struct Foo {
std::unique_ptr <int> bar;
Foo() = default;
Foo(Foo&&) = default;
~Foo (void) {} // This Line
};
Explicit destructor makes compiler not generate default move constructor and default assignement move operator, and those two are required by unique_ptr for ownership transfering. So you must implement those if you want to implement destructor:
Foo(){}
Foo &operator=(Foo &&o) {
if (this != &o)
bar = std::move(o.bar);
return *this;
}
Foo(Foo &&o) : bar(std::move(o.bar)) {}
because Foo should not be copied, only moved I suggest also explicitly deleting copy constructor and assignment operator:
Foo(Foo const &) = delete;
Foo &operator=(Foo const &) = delete;
[edit]
Actually, it is better explained by rule of five which can be easily found on google, if you implement any of the :
destructor
copy constructor
move constructor
copy assignment operator
move assignment operator
you should consider implementing all of them (or deleting), and this is especially true when using unique_ptr which makes use of move semantics.
When you comment out destructor, then you enter rule of zero.

why does deleting move constructor cause vector to stop working

If I inhibit the move constructor in a class, I can no longer use it in a vector:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
int i_;
};
int main()
{
std::vector<Foo> foo;
foo.push_back(Foo(1));
}
Why is this so?
Summary
Don't delete the move members.
Assuming your compiler is completely C++11 conforming, then explicitly deleting the move constructor will also implicitly declare the following:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
That is if you declare a move constructor (or move assignment operator), and do not declare copy members, they are implicitly declared as deleted. So your complete class Foo is as if:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
Foo(const Foo&) = delete; // implicitly declared
Foo& operator=(const Foo&) = delete; // implicitly declared
int i_;
};
Now vector<Foo>::push_back(Foo(1)) requires that Foo be MoveConstructible. MoveConstructible could be satisfied by an accessible move constructor, or even by an accessible copy constructor. But Foo has neither. To fix you could:
class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(const Foo&) = default;
Foo& operator=(const Foo&) = default;
int i_;
};
I.e. default the copy members and remove the deleted move member.
In general it is not a good idea to explicitly delete the move members. If you want a class to be copyable but not "movable", just declare exactly as you would in C++03: declare/define your copy members. You can let the copy members be compiler-generated with = default, and that still counts as a user-declaration. And don't declare move members. Move members that don't exist are not the same as deleted move members.
Deleted move members mean you can not construct a copy of Foo from an rvalue, even if the copy constructor would have worked fine to do so. This is rarely the desired intent.
Even if you want your class to not be copyable nor movable, it is better to just delete the copy members and leave the move members undeclared (meaning they won't exist). If you're ever reviewing code (including your own), and see deleted move members, they are almost certainly incorrect, or at the very best superfluous and confusing.
Some day someone will come up with a good use case for deleted move members. But it will be a rare use case. If you see such a pattern in code, you should expect the code author to have a very good explanation. Otherwise, deleted move members are likely to just be incorrect (at best superfluous). But on the bright side this error will show itself at compile time, instead of at run time (as in your example).
Here is a summary chart of what the compiler will implicitly do when you explicitly declare any of the special members. Those squares colored red represent deprecated behavior.
= default and = delete count as user-declared.
Click here if you would like to view the full slide deck.
You say:
I can no longer use it in a vector:
However, since C++11, requirements for use in a vector are minimal; instead, each vector operation has its own requirements. So, in fact you can use Foo in a vector but you are restricted to operations that have no possibility of requiring the object to be moved or copied.
For example you can write:
std::vector<Foo> w(5);
w.pop_back();
and you can also call w[0].some_member_function(); and so on.
However you cannot write any of the following, due to the way you defined Foo:
std::vector<Foo> w = { 1, 2, 3 }; // Requires moving elements out of initializer_list
w.resize(5); // Resize may mean reallocation which may require elements to be moved to new location
w.push_back(5); // ditto
w.erase(w.begin()); // Erasing an element may require other elements to move to fill its place
w[0] = 1; // operator= is implicitly deleted as described in Howard's answer (but you could explicitly default it to make this work)
If you want to have Foo be copyable, but not movable -- and also have any move scenarios fall back to using a copy -- then the only way to do that is to not declare any move-constructor at all; and force inhibition of the compiler-generated move-constructor by declaring a destructor, copy-constructor, and/or copy-assignment operator as shown in Howard's table.
See the last code sample in Howard's answer for an example of this.
To be clear there are actually several different possible statuses for the move constructor, not all shown in Howard's table:
Defined as deleted, and user-defined
Defined as deleted, and not user-defined (this happens when a member is not movable)
Defaulted and user-defined
Defaulted and not user-defined
User-provided (i.e. user-defined and neither defaulted nor deleted)
Not declared
In cases 1, 3, 4, 5 above, the move constructor is found by overload resolution. In cases 2 and 6 it is not found by overload resolution; and a copy/move scenario such as push_back(Foo(1)); will fall back to a copy constructor (if any).