13

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?

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
MK.
  • 3,907
  • 5
  • 34
  • 46

2 Answers2

41

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    It may be helpful to copy some of this content to [your answer here](http://stackoverflow.com/a/26492184/1708801) I actually found this looking for more details, +1. – Shafik Yaghmour Oct 21 '14 at 19:10
  • If I want to make a class copyable but not movable, is it really enough to let the copy members be implicitly generated? This [answer](http://stackoverflow.com/a/4944131) says the move members will be generated unless there is a user declared move member or destructor. – guini Oct 22 '14 at 16:32
  • 1
    @guini: Move members will be implicitly provided unless there is a user-declared destructor, user-declared copy constructor, or user-delcared copy assignment operator. See this chart which summarizes everything: https://plus.google.com/u/0/+MartinMoene/posts/gdjLfjD9Pxm – Howard Hinnant Oct 22 '14 at 16:47
  • Thanks for the overview! My question was about the _If you want a class to be copyable but not "movable"...declare/define your copy members (or let them be implicitly generated)_ part of your answer. I thought you meant move members won't be generated, if there are implicitly generated copy members. – guini Oct 22 '14 at 17:02
  • 1
    @guini: I see, thanks for pointing that out. It was poorly worded. I have edited to clarify. – Howard Hinnant Oct 22 '14 at 17:06
  • @M.M: Thanks, added a one-sentence summary to the top of the answer. – Howard Hinnant Mar 04 '16 at 14:57
  • When you say "deprecated behavior" for the defaulted copy members, what exactly do you mean by "deprecated behavior"? Did C++11 (or 14) deprecate the defaulted status of those members with the intention of a future C++ standard changing the behavior so they're not declared? – Cornstalks May 07 '16 at 22:25
  • 1
    @Cornstalks: Basically yes. C++11 deprecated the implicit defaulting of the copy constructor (for example) when there is a user-declared destructor or user-declared copy assignment operator. That means a future standard may change that behavior to something else (that something else has not been specified). To avoid depending on deprecated behavior, if you declare any of the destructor or either copy member, then declare both copy members. By "declare" I mean explicitly provide, explicitly default or explicitly delete. Failure to do so doesn't pass code review on my team. – Howard Hinnant May 08 '16 at 18:00
0

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:

  1. Defined as deleted, and user-defined
  2. Defined as deleted, and not user-defined (this happens when a member is not movable)
  3. Defaulted and user-defined
  4. Defaulted and not user-defined
  5. User-provided (i.e. user-defined and neither defaulted nor deleted)
  6. 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).

M.M
  • 138,810
  • 21
  • 208
  • 365