c++c++11copy-constructormove-semanticslanguage-design

Why are copy operations deleted when move operations are declared?


When a class explicitly declares a copy operation (i.e., a copy constructor or copy assignment operator), move operations are not declared for the class. But when a class explicitly declares a move operation, the copy operations are declared as deleted. Why does this asymmetry exist? Why not just specify that if a move operation is declared, no copy operations will be declared? From what I can tell, there would not be any behavioral difference, and there would be no need for the asymmetric treatment of move and copy operations.

[For people who like citations of the standard, the lack of declaration of move operations for classes with copy operation declarations is specified in 12.8/9 and 12.8/20, and the deleted copy operations for classes with move operation declarations are specified in 12.8/7 and 12.8/18.]


Solution

  • When a class would be moved but for the fact that no move constructor is declared, the compiler falls back to copy constructor. In the same situation, if move constructor is declared as deleted, the program would be ill-formed. Thus, if move constructor were implicitly declared as deleted, a lot of reasonable code involving existing pre-C++11 classes would fail to compile. Things like myVector.push_back(MyClass())

    This explains why move constructor cannot be implicitly declared deleted when copy constructor is defined. This leaves the question of why copy constructor is implicitly declared deleted when move constructor is defined.

    I don't know the exact motivation of the committee, but I have a guess. If adding a move constructor to existing C++03-style class were to remove a (previously implicitly defined) copy constructor, then existing code using this class may change meaning in subtle ways, due to overload resolution picking unexpected overloads that used to be rejected as worse matches.

    Consider:

    struct C {
      C(int) {}
      operator int() { return 42; }
    };
    
    C a(1);
    C b(a);  // (1)
    

    This is a legacy C++03 class. (1) invokes an (implicitly defined) copy constructor. C b((int)a); is also viable, but is a worse match.

    Imagine that, for whatever reason, I decide to add an explicit move constructor to this class. If the presence of move constructor were to suppress the implicit declaration of copy constructor, then a seemingly unrelated piece of code at (1) would still compile, but silently change its meaning: it would now invoke operator int() and C(int). That would be bad.

    On the other hand, if copy constructor is implicitly declared as deleted, then (1) would fail to compile, alerting me to the problem. I would examine the situation and decide whether I still want a default copy constructor; if so, I would add C(const C&)=default;