Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does deleting move constructor cause vector to stop working

Tags:

c++

c++11

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?

like image 799
MK. Avatar asked Mar 03 '13 01:03

MK.


1 Answers

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.

enter image description here

= default and = delete count as user-declared.

Click here if you would like to view the full slide deck.

like image 122
Howard Hinnant Avatar answered Oct 05 '22 13:10

Howard Hinnant