Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do deleted move semantics cause problems with std::vector? [duplicate]

Tags:

c++

c++11

After doing a bit of research, I see that C++11 has a defect with allocators that require the type to be movable/copyable. I'm sure that this is the cause of this problem, however I am confused about the behavior between deleted and not declared move semantics.

I have the following code which fails to compile on both MSVC12 and Clang:

#include <vector>

class Copyable
{
public:
   Copyable() = default;

   Copyable(Copyable const& other)
      : m_int(other.m_int)
   {}

   Copyable& operator= (Copyable const& other)
   {
      m_int = other.m_int;
      return *this;
   }

   Copyable(Copyable&&) = delete;
   Copyable& operator= (Copyable&&) = delete;

private:
   int m_int = 100;
};

int main()
{
   std::vector<Copyable> objects;
   objects.push_back(Copyable{});
}

This fails to compile on MSVC with:

xmemory0(600): error C2280: 'Copyable::Copyable(Copyable &&)' : attempting to reference a deleted function

And on Clang (live sample):

new_allocator.h:120:23: error: call to deleted constructor of 'Copyable'

In both cases, when I remove the explicitly deleted move construct/assign methods, the code compiles. AFAIK when you declare copy assign/construct methods, the compiler does not implicitly declare the corresponding move members. So they should still be effectively deleted, right? Why does the code compile when I remove the explicit deletion of move construct/assign?

What is a good workaround for this C++11 defect in general? I do not want my objects to be movable (but they are copyable).

like image 803
void.pointer Avatar asked Oct 21 '14 14:10

void.pointer


People also ask

Does std :: move make a copy?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.

Does Emplace_back make a copy?

So you can emplace_back does use the desired constructor to create the element and call copy constructor when it need to grow the storage. You can call reserve with enough capacity upfront to avoid the need to call copy constructor.

When should I use move semantics?

That's what rvalue references and move semantics are for! Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.

What does std :: move actually do?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.


2 Answers

Deleting a function is not the same as not declaring it.

A deleted function is declared and participates in overload resolution, but if you attempt to call it an error is produced.

If you fail to declare your move constructor, the compiler will not create one as you created a copy constructor. Overload resolution on an rvalue will find your copy constructor, which is probably what you want.

What you said with your foo(foo&&)=delete was "if anyone tries to move construct this object, generate an error".

I can illustrate the difference here:

void do_stuff( int  x ) { std::cout << x << "\n"; }
void do_stuff( double ) = delete;

void do_stuff2( int  x ) { std::cout << x << "\n"; }
//void do_stuff2( double ) = delete;

int main() {
  do_stuff(3); // works
  //do_stuff(3.14); // fails to compile
  do_stuff2(3); // works
  do_stuff2(3.14); // works, calls do_stuff2(int)
}

the only part with your above problem that makes this a bit more confusing is that special member functions are automatically created or not based off slightly arcane rules.

like image 61
Yakk - Adam Nevraumont Avatar answered Oct 21 '22 22:10

Yakk - Adam Nevraumont


Copyable(Copyable&&) = delete;
Copyable& operator= (Copyable&&) = delete;

Unless you are an expert in move semantics (and I mean, really knowledgeable), never delete the special move members. It won't do what you want. If you review someone else's code that has done this, call it out. The explanation has to be really solid, and not "because I don't want the type to move."

Just don't do it.

The proper way to do what you want is to simply declare/define your copy members. The move members will be implicitly inhibited (not deleted, but actually not there). Just write C++98/03.

For more details, see this answer.

like image 24
Howard Hinnant Avatar answered Oct 21 '22 22:10

Howard Hinnant