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 vonImplicitDeleteei @ wird ein perfekter impliziter Verschiebungskonstruktor generiert, der @ aufrufImplicitDelete 's Kopie ctor.

So was ist das Grundprinzip hinter dem Ermöglichen vonImplicitm 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 wieExplicitDeleteImplicitDelete 's gelöschter Verschiebector führt zu einem gelöschten impliziten Verschiebector inImplicit (auf die gleiche Weise wieExplicitDelete macht das mitImplicitDelete)Implicit wird nichtmove-constructibleZusammenstellung desstd::move Zeile schlägt in meinem Codebeispiel völlig fehl

Oder der Compiler greift zurück um ctor @ zu kopierebenfall zumExplicitDelete:

ExplicitDeleteer Kopierkonstruktor von @ wird in all @ aufgerufemoves, genau wie beiImplicitDeleteImplicitDelete 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;
}

Antworten auf die Frage(2)

Ihre Antwort auf die Frage