Warum werden implizit und explizit gelöschte Verschiebungskonstruktoren unterschiedlich behandelt?
Was ist der Begründung hinter der unterschiedlichen Behandlung implizit und explizit gelöschter Verschiebungskonstruktoren im C ++ 11-Standard in Bezug auf die implizite Erzeugung von Verschiebungskonstruktoren, die Klassen enthalten / erben?
Ändert C ++ 14 / C ++ 17 etwas? (Außer DR1402 in C ++ 14)
Hinweis: Ich verstehe, was passiert. Ich verstehe, dass es den Regeln des C ++ 11-Standards entspricht. Ich bin an der Begründung für diese Regeln interessiert, die dieses Verhalten implizieren so ist es, weil der Standard so sagt).
Eine Klasse annehmenExplicitDelete
mit einem explizit gelöschten move ctor und einem explizit voreingestellten copy ctor. Diese Klasse ist nichtmove constructible
obwohl einkompatibe copy ctor ist verfügbar, da die Überladungsauflösung den Verschiebungskonstruktor auswählt und beim Kompilieren aufgrund seiner Löschung fehlschlägt.
Eine Klasse annehmenImplicitDelete
welches entweder enthält oder von @ erExplicitDelete
und macht sonst nichts. Die Verschiebung dieser Klasse wird implizit als gelöscht deklariert, da C ++ 11 ctor rules verschieben. Diese Klasse wird jedoch weiterhin @ seimove constructible
über seine Kopie ctor. (Hat diese letzte Aussage mit der Auflösung von @ zu tu DR1402?)
Dann eine KlasseImplicit
enthaltend / erbend vonImplicitDelete
ei @ wird ein perfekter impliziter Verschiebungskonstruktor generiert, der @ aufrufImplicitDelete
's Kopie ctor.
So was ist das Grundprinzip hinter dem Ermöglichen vonImplicit
m sich implizit bewegen zu können uImplicitDelete
nicht implizit bewegen können?
In der Praxis, wennImplicit
undImplicitDelete
haben einige schwere bewegliche Mitglieder (denkevector<string>
), Ich sehe keinen Grund, dassImplicit
sollte @ weit überlegen seImplicitDelete
in Bewegung Leistung.ImplicitDelete
könnte noch kopierenExplicitDelete
von seiner impliziten Verschiebung ctor - genau wieImplicit
macht mitImplicitDelete
.
Für mich scheint dieses Verhalten inkonsistent zu sein. Ich würde es konsistenter finden, wenn eines dieser beiden Dinge passiert:
Der Compiler behandelt sowohl die implizit als auch die explizit gelöschten Verschiebungen gleich:
ImplicitDelete
wird nichtmove-constructible
, so wieExplicitDelete
ImplicitDelete
's gelöschter Verschiebector führt zu einem gelöschten impliziten Verschiebector inImplicit
(auf die gleiche Weise wieExplicitDelete
macht das mitImplicitDelete
)Implicit
wird nichtmove-constructible
Zusammenstellung desstd::move
Zeile schlägt in meinem Codebeispiel völlig fehlOder der Compiler greift zurück um ctor @ zu kopierebenfall zumExplicitDelete
:
ExplicitDelete
er Kopierkonstruktor von @ wird in all @ aufgerufemove
s, genau wie beiImplicitDelete
ImplicitDelete
bekommt einen richtigen impliziten Zug ctor(Implicit
ist in diesem Szenario unverändert)Die Ausgabe des Codebeispiels zeigt an, dass dasExplicit
member wird immer verschoben.Hier ist das voll funktionsfähige Beispiel:
#include <utility>
#include <iostream>
using namespace std;
struct Explicit {
// prints whether the containing class's move or copy constructor was called
// in practice this would be the expensive vector<string>
string owner;
Explicit(string owner) : owner(owner) {};
Explicit(const Explicit& o) { cout << o.owner << " is actually copying\n"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving\n"; }
};
struct ExplicitDelete {
ExplicitDelete() = default;
ExplicitDelete(const ExplicitDelete&) = default;
ExplicitDelete(ExplicitDelete&&) noexcept = delete;
};
struct ImplicitDelete : ExplicitDelete {
Explicit exp{"ImplicitDelete"};
};
struct Implicit : ImplicitDelete {
Explicit exp{"Implicit"};
};
int main() {
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); // expect 1x ImplicitDelete's copy and 1x Implicit's move
return 0;
}