2016-05-17 13 views
10

Nach der Norm,Standard bewegt Konstruktor/Zuordnung und gelöscht Copykonstruktor/Zuordnung

Wenn die Definition einer Klasse X nicht explizit einen Zug Konstruktor deklariert, wird ein implizit als ausgefallen, wenn und nur erklärt wenn

- X nicht einen benutzer erklärt hat Copykonstruktor,

- X nicht einen benutzer erklärt hat Kopierzuweisungsoperator,

- X nicht h ave ein vom Benutzer deklarierter Zuweisungsoperator und

- X hat keinen vom Benutzer deklarierten Destruktor.

nun folgende versagt

# include <utility> 

class Foo 
{ 
public: 
    Foo() = default; 
    Foo(Foo const &) = delete; 
}; 

int main() 
{ 
    Foo f; 
    Foo g(std::move(f)); // compilation fails here 
    return 0; 
} 

So scheint es zu kompilieren, dass eine gelöschte Funktion als benutzerdefinierte betrachtet wird, was Sinn macht (es ist nicht seine Standardimplementierung ist). In diesem speziellen Fall jedoch, wie würde gelöschte Kopie Konstruktor/Zuweisung Mess-Standard-Konstruktor/Zuordnung verschieben?

Ich denke, diese Frage hat praktische Bedeutung, weil manuelle Generierung und esp. die Aufrechterhaltung solcher Standardfunktionen ist fehleranfällig, während zur gleichen Zeit die (rechtschaffene) Zunahme der Verwendung von Klassen wie std::unique_ptr als Klassenmitglieder nicht kopierbare Klassen viel häufiger als zuvor gemacht hat.

+0

Nicht sicher, Ihre Frage gut zu verstehen, aber warum move ctor nicht standardmäßig? d. h. 'Foo (Foo &&) = default;' –

Antwort

12

user-declared bedeutet entweder entweder Benutzer bereitgestellten (definiert durch den Benutzer), explizit vorbelegt (= default) oder explizit gelöscht (= delete) im Gegensatz zu implizit vorbelegt/gelöscht werden (wie Ihre Bewegung Konstruktor).

Also in Ihrem Fall ja der Umzug Konstruktor ist implizit gelöscht, weil die Kopie-Konstruktor explizit gelöscht ist (und damit benutzer erklärt).

In diesem speziellen Fall, wie würde gelöschte Kopie Konstruktor/Zuweisung Messstandardkonstruktor/Zuordnung bewegen?

Es würde nicht, aber der Standard macht keinen Unterschied zwischen diesem Fall und einem komplizierten.

Die kürzeste Antwort ist, dass mit einem implizit definiert move-Konstruktor mit einem explizit gelöscht Kopie-Konstruktor könnte in einigen Fällen gefährlich sein, das gleiches, wenn Sie einen benutzerdefinierten destructor und keine benutzerdefiniert Kopie-Konstruktor (siehe rule of three/five/zero). Nun können Sie argumentieren, dass ein benutzerdefinierter Destruktor den Kopierkonstruktor nicht löscht, aber dies ist einfach ein Fehler in der Sprache, der nicht entfernt werden kann, weil es viele alte (schlechte) Programme kaputt machen würde. Um Bjarne Stroustrup zu zitieren:

In einer idealen Welt, ich glaube, wir auf „keine Generation“ als Standard und bieten eine wirklich einfache Notation für sich entscheiden würde [...] „Gib mir alle üblichen Operationen.“ Eine "no default operations" -Richtlinie führt außerdem zu Kompilierungszeitfehlern (die wir leicht beheben sollten), während eine Richtlinie zum Generieren von Vorgängen standardmäßig zu Problemen führt, die erst zur Laufzeit erkannt werden.

Sie können mehr darüber in N3174=10-0164 lesen.

Beachten Sie, dass die meisten Leute die rule of three/five/zero folgen, und meiner Meinung nach sollten Sie. Durch das implizite Löschen des Standard-Move-Konstruktors "schützt" der Standard Sie vor Fehlern und sollte Sie lange Zeit davor schützen, indem Sie in einigen Fällen den Kopierkonstruktor löschten (siehe Bjarnes Beitrag).

Literaturhinweise, wenn Sie interessiert sind:

Ich denke, diese Frage praktische Bedeutung hat, weil manuelle Generation und esp. die Aufrechterhaltung solcher Standardfunktionen ist fehleranfällig, während zur gleichen Zeit die (rechtschaffene) Zunahme der Verwendung von Klassen wie std::unique_ptr als Klassenmitglieder nicht kopierbare Klassen viel häufiger als zuvor gemacht hat.

class Foo { 
public: 
    Foo() = default; 
    Foo(Foo const &) = delete; 
    Foo(Foo&&) = default; 
}; 

Sie erhalten ein nicht-kopierbaren Objekt mit einem Standard-Bewegung Konstruktor, und meiner Meinung nach diesen expliziten Erklärungen sind besser als implizites: als explizit vorgegeben wird dieses Problem lösen

den Umzug Konstruktor Marking (z. B. indem der Move-Konstruktor nur als default deklariert wird, ohne den Kopierkonstruktor zu löschen).

+0

@NathanOliver Es gibt einige hier http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3153.htm. – Holt

+0

Danke. Ich habe auch welche gefunden. Dies scheint mir die beste Antwort zu sein. – NathanOliver

-1

Das Verhalten kann im wesentlichen mit veranschaulicht werden:

void foo(const int& i) 
{ 
    std::cout << "copy"; 
} 

void foo(int&& i) 
{ 
    std::cout << "move"; 
} 

Wenn beide Überlastungen vorhanden sind, die Überlast für int&& rvalues ​​ausgewählt wird, während die Überlast für const int& lvalues ​​ausgewählt ist. Wenn Sie die int&& Überladung entfernen, wird const int& aufgerufen (ergo, kein Fehler) auch für rvalues. Dies ist im Wesentlichen, was in diesem Fall passiert:

class Foo 
{ 
public: 
    Foo() = default; 
    Foo(Foo const &) = delete; 
}; 

Überlastung Auflösung sieht nur einen Kandidaten, aber da es explizit gelöscht wird, wird das Programm schlecht ausgebildet.

Die nicht-normative Note unter dem Abschnitt, den Sie stellt klar, zitierte das ist, was geschieht:

[Anmerkung: Wenn der Umzug Konstruktor nicht implizit deklariert wird oder explizit geliefert, Ausdrücke, die sonst die aufgerufen hätte move constructor kann stattdessen einen Kopierkonstruktor aufrufen. - Endnote ]

+0

Ich hoffe, * das ist ein Rache-Downvote. –

1

Wie Sie erwähnt, aus §12.8

Wenn die Definition einer Klasse X einen Umzug Konstruktor nicht explizit deklariert, wird man implizit als wenn und nur wenn

  • X ein benutzer erklärt hat nicht vorbelegt deklariert werden Copykonstruktor,

  • [...]

Beachten Sie die Benutzer deklariert. Aber wenn man sich §8.4.3:

Eine Funktionsdefinition der Form:

Attribut-specifier-Seq opt Decl-specifier-Seq opt declarator virt-Spezifizierer-Seq opt = löschen;

heißt eine gelöschte Definition. Eine Funktion mit einer gelöschten Definition wird auch als gelöschte Funktion bezeichnet.

Ein Programm, das implizit oder explizit auf eine gelöschte Funktion verweist, außer es deklarieren, ist schlecht gebildet.

So definiert der Standard delete d Funktionen als benutzer erklärt (wie Sie von oben sehen), obwohl sie delete d sind und nicht verwendet werden kann.

Dann, nach §12.8, ist der implizite Move-Konstruktor nicht definiert, wegen des vom Benutzer deklarierten (mit = delete;) Kopierkonstruktors.