2015-08-24 9 views
5

Ich bin in eine Situation gekommen, die ziemlich interessant ist, da der Code, an dem ich arbeite, kompiliert, obwohl ich überrascht bin, dass ich das möchte.Automatisch generierter Move Konstruktor mit nicht beweglichen Elementen

Die Situation ist dies. Ich habe eine Klasse mit gelöscht bewegen und Kopierkonstruktoren, die benutzerdefinierten Zuweisungsoperator hat:

struct A { 
    A() { } 
    A(const A&) = delete; 
    A(A&&) = delete; 

    A& operator=(const A&) { return *this; } 
    A& operator=(A&&) { return *this; } 
}; 

Und ich habe eine andere Klasse mit A als einzigem Mitglied. In dieser Klasse I definiert den Kopierkonstruktor aber ich hielt den Umzug Konstruktor als Standard und definiert den Zuweisungsoperator durch einen Aufruf der Swap-Funktion:

class B{ 
public: 
    A a; 

    B() 
    : a{} 
    { } 

    B(const B&) 
    : a{} 
    { } 

    B(B&& other) = default; 
}; 

int main() { 
    B b1; 
    B b2(std::move(b1)); // compiles?? 
} 

Warum wird die Standard-Bewegung Konstruktor Arbeit, wenn man bedenkt, dass es nicht einfach anrufen der Umzugs- oder Kopierkonstruktor A? Ich benutze gcc 4.8.4.

Antwort

7

Meine ursprüngliche Antwort war falsch, also fange ich neu an.


In [class.copy], haben wir:

A notleidenden copy/ bewegen Konstruktor für eine Klasse X als gelöscht definiert (8.4.3), wenn X hat:
- [...]
- ein potentiell konstruierter Unterobjekttyp M (oder Array davon), der nicht kopiert/verschoben werden kann, da Überladungsauflösung (13.3), wie sie auf Ms entsprechenden Konstruktor angewendet wird, zu einer Mehrdeutigkeit oder einer Funktion führt gelöscht oder unzugänglich aus dem notleidenden Konstruktor
- [...]

Das Aufzählungspunkt auf B(B&& other) = default; gilt, so dass Bewegung Konstruktor als gelöscht definiert ist. Dies scheint Kompilation mit std::move() zu brechen, aber wir haben auch (über Auflösung von defect 1402):

A notleidenden Bewegung Konstruktor, der definiert ist als durch Überlastung Auflösung ignoriert gelöscht (13.3, 13.4). [Hinweis: Ein gelöschter move -Konstruktor würde andernfalls die Initialisierung von einem rvalue stören, der stattdessen den Kopierkonstruktor verwenden kann. -Endnote]

Ignorieren ist der Schlüssel. Wenn wir also tun:

B b1; 
B b2(std::move(b1)); 

Auch wenn der Umzug Konstruktor für B gelöscht wird, wird dieser Code wohlgeformt, weil der Umzug Konstruktor einfach nicht in der Überladungsauflösung beteiligt ist und die Kopie Konstruktor wird stattdessen genannt. Daher ist B MoveConstructible - auch wenn Sie es nicht über seinen Move-Konstruktor konstruieren können.

+0

Ich habe ein paar Couts in der Kopie und Zuweisung hinzugefügt und es scheint zu bestätigen Ihre Hypothese, dass, nicht in der Lage, den Move-Konstruktor korrekt zu kompilieren, es einfach auf die Kopie zurückkehrt.Ich bin kein Experte für die Feinheiten des Standards, aber es sieht so aus, als ob sich die Standarddeklaration in einer Vorlage wie SFINAE verhält. – Triskeldeian

+0

Ein defaulthafter Move-Konstruktor, der als gelöscht definiert ist, wird von der Überladungsauflösung ignoriert. Siehe N4527 [class.copy]/p11; auch [N3667] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3667.html) –

+0

@ T.C. Neue Antwort klingt ungefähr richtig? – Barry