Warum werden Kopiervorgänge gelöscht, wenn Verschiebeoperationen deklariert werden?

8

Wenn eine Klasse explizit eine Kopieroperation deklariert (d. h. einen Kopierkonstruktor oder einen Kopierzuweisungsoperator), werden Bewegungsoperationen für die Klasse nicht deklariert. Wenn eine Klasse jedoch explizit eine Verschiebungsoperation deklariert, werden die Kopiervorgänge als gelöscht deklariert. Warum existiert diese Asymmetrie? Warum geben Sie nicht einfach an, dass, wenn eine Verschiebeoperation deklariert wird, keine Kopieroperationen deklariert werden? Von dem, was ich sagen kann, würde es keinen Verhaltensunterschied geben, und die asymmetrische Behandlung von Bewegungs- und Kopiervorgängen wäre nicht notwendig.

[Für Personen, die Zitate des Standards mögen, ist die fehlende Deklaration von Verschiebeoperationen für Klassen mit Kopieroperationsdeklarationen in 12.8 / 9 und 12.8 / 20 spezifiziert, und die gelöschten Kopieroperationen für Klassen mit Verschiebeoperationsdeklarationen sind angegeben in 12.8 / 7 und 12.8 / 18.]

    
KnowItAllWannabe 14.08.2014, 03:34
quelle

3 Antworten

7

Wenn eine Klasse verschoben wird, aber kein Bewegungskonstruktor deklariert wird, fällt der Compiler auf den Kopierkonstruktor zurück. Wenn in der gleichen Situation der Move-Konstruktor als gelöscht deklariert ist, ist das Programm schlecht formatiert. Wenn also move constructor implizit als deleted deklariert würde, würde viel vernünftiger Code, der existierende pre-C ++ 11-Klassen involviert, nicht kompiliert werden können. Dinge wie myVector.push_back(MyClass())

Dies erklärt, warum der move -Konstruktor nicht implizit als gelöscht deklariert werden kann, wenn der Kopierkonstruktor definiert ist. Dies lässt die Frage offen, warum der Kopierkonstruktor implizit als gelöscht deklariert wird, wenn der Move-Konstruktor definiert ist.

Ich kenne die genaue Motivation des Ausschusses nicht, aber ich habe eine Vermutung. Wenn das Hinzufügen eines Verschiebungskonstruktors zur vorhandenen C ++ 03-style-Klasse einen (zuvor implizit definierten) Kopierkonstruktor entfernt, kann der vorhandene Code, der diese Klasse verwendet, die Bedeutung auf subtile Weise ändern, da Überladungsauflösung unerwartete Überladungen auswählt als schlechteste Übereinstimmungen abgelehnt.

Überlegen Sie:

%Vor%

Dies ist eine ältere C ++ 03-Klasse. (1) ruft einen (implizit definierten) Kopierkonstruktor auf. C b((int)a); ist auch lebensfähig, aber ist eine schlechtere Übereinstimmung.

Stellen Sie sich vor, dass ich aus irgendeinem Grund einen expliziten Move-Konstruktor zu dieser Klasse hinzufüge. Wenn das Vorhandensein des move -Konstruktors die implizite Deklaration des Kopierkonstruktors unterdrücken würde, würde ein scheinbar nicht verwandter Teil des Codes bei (1) immer noch kompilieren, aber seine Bedeutung stillschweigend ändern: er würde nun operator int() und C(int) aufrufen. Das wäre schlecht.

Wenn andererseits der Kopierkonstruktor implizit als gelöscht deklariert ist, dann würde (1) nicht kompiliert werden, was mich auf das Problem aufmerksam macht. Ich würde die Situation untersuchen und entscheiden, ob ich noch einen Standard-Kopierkonstruktor haben möchte; Wenn ja, würde ich C(const C&)=default;

hinzufügen     
Igor Tandetnik 14.08.2014, 03:51
quelle
2
  

Warum existiert diese Asymmetrie?

Abwärtskompatibilität und weil die Beziehung zwischen Kopieren und Verschieben bereits asymmetrisch ist. Die Definition von MoveConstructible ist ein Spezialfall von CopyConstructible, was bedeutet, dass alle CopyConstructible-Typen auch MoveConstructible-Typen sind. Das ist richtig, weil ein Kopierkonstruktor, der einen Verweis auf const verwendet, sowohl rvalues ​​als auch lvalues ​​behandelt.

Ein kopierbarer Typ kann aus rvalues ​​ohne einen Move-Konstruktor initialisiert werden (er ist möglicherweise nicht so effizient wie er mit einem Move-Konstruktor sein könnte).

Ein Kopierkonstruktor kann auch verwendet werden, um eine "Verschiebung" in implizit definierten Bewegungskonstruktoren abgeleiteter Klassen durchzuführen, wenn das Basis-Unterobjekt verschoben wird.

Ein Kopierkonstruktor kann also als "entarteter Move-Konstruktor" betrachtet werden. Wenn also ein Typ einen Kopierkonstruktor hat, braucht er keinen Move-Konstruktor, er ist also schon MoveConstructible, also einfach den Move-Konstruktor nicht zu deklarieren ist akzeptabel.

Das Gegenteil ist nicht der Fall, ein beweglicher Typ ist nicht unbedingt kopierbar, z. Nur-Bewegung-Typen. In diesen Fällen bietet das Löschen des Kopierkonstruktors und der Zuweisung eine bessere Diagnose als das bloße Nicht-Deklarieren und das Ermitteln von Fehlern beim Binden von Lvalues ​​an Rvalue-Referenzen.

  

Warum geben Sie nicht einfach an, dass, wenn eine Verschiebungsoperation deklariert ist, keine Kopieroperationen deklariert werden?

Bessere Diagnose und explizitere Semantik. "Defined as deleted" ist der C ++ 11-Weg, klar zu sagen "diese Operation ist nicht erlaubt", anstatt einfach aus Versehen weggelassen zu werden oder aus einem anderen Grund zu fehlen.

Der Sonderfall "nicht deklariert" für Move-Konstruktoren und Move-Zuweisungsoperatoren ist ungewöhnlich und wegen der oben beschriebenen Asymmetrie besonders, aber Spezialfälle werden in der Regel für einige wenige Fälle gut aufbewahrt (hier ist zu beachten, dass "nicht deklariert "kann auch auf den Standardkonstruktor angewendet werden."

Bemerkenswert ist auch, dass einer der Paragraphen, auf die Sie verweisen, [class.copy] p7, (Hervorhebung von mir) sagt:

  

Wenn die Klassendefinition keinen Kopierkonstruktor explizit deklariert, wird einer implizit deklariert . Wenn die Klassendefinition einen Verschiebungskonstruktor oder einen Verschiebungszuweisungsoperator deklariert, wird der implizit deklarierte Kopierkonstruktor als gelöscht definiert. Ansonsten ist es als default definiert (8.4). Letzterer Fall ist veraltet, wenn die Klasse über einen vom Benutzer deklarierten Kopierzuweisungsoperator oder einen vom Benutzer deklarierten Destruktor verfügt.

"Letzterer Fall" bezieht sich auf den "andernfalls ist es als defaulted" Teil definiert. Absatz 18 hat eine ähnliche Formulierung für den Kopierzuweisungsoperator.

Die Absicht des Komitees ist also, dass in einigen zukünftigen Versionen von C ++ andere Arten von speziellen Member-Funktionen dazu führen, dass der Kopierkonstruktor und der Kopierzuweisungsoperator gelöscht werden. Wenn Ihre Klasse einen benutzerdefinierten Destruktor benötigt, wird das implizit definierte Kopierverhalten wahrscheinlich nicht das Richtige tun. Diese Änderung wurde aus Gründen der Abwärtskompatibilität nicht für C ++ 11 oder C ++ 14 vorgenommen, aber die Idee ist, dass in einigen zukünftigen Versionen, um zu verhindern, dass der Kopierkonstruktor und der Kopierzuweisungsoperator gelöscht werden, diese explizit deklariert werden müssen Definieren Sie sie als Standardwerte.

Löschen von Kopierkonstruktoren, wenn sie nicht das Richtige tun, ist der allgemeine Fall, und "nicht deklariert" ist ein Spezialfall nur für Bewegungskonstruktoren, da der Kopierkonstruktor den entarteten Zug sowieso bereitstellen kann.

    
Jonathan Wakely 14.08.2014 14:09
quelle
0

Es ist im Wesentlichen zu vermeiden, dass migrierter Code unerwartete verschiedene Aktionen ausführt.

Kopieren und Verschieben erfordern ein gewisses Maß an Kohärenz, so dass C ++ 11 - wenn Sie nur eins deklarieren - das andere unterdrückt.

Bedenken Sie Folgendes:

%Vor%

Angenommen, Sie schreiben dies in C ++ 03.

Angenommen, ich kompiliere es später in C ++ 11. Wenn kein ctor deklariert ist, wird durch die Standardverschiebung eine Kopie erstellt (das endgültige Verhalten entspricht also C ++ 03).

Wenn copy deklariert ist, wird move gelöscht und sine C&& verfällt in C const& Die dritte Anweisung ergibt eine Kopie von einem temporären. Dies ist immer noch ein C ++ 03 identisches Verhalten.

Wenn ich später einen move ctor hinzufüge, bedeutet das, dass ich das Verhalten von C ändere (etwas, das Sie nicht geplant haben, als Sie C in C ++ 03 definiert haben) und ein bewegliches Objekt nicht muss kopierbar sein (und umgekehrt). Der Compiler geht davon aus, dass die dafault-Kopie nicht mehr adäquat ist, wenn sie beweglich gemacht wird. Es liegt an mir, es in Übereinstimmung mit dem Umzug zu implementieren oder - wenn ich es als angemessen empfunden habe - stelle das C(const C&)=default;

wieder her     
Emilio Garavaglia 14.08.2014 13:07
quelle