2016-01-27 7 views
6

folgendes Programm vor:Rückkehr Provisorien vom Typ mit gelöscht verschieben/kopieren ctor

#include<iostream> 
using namespace std; 

struct S 
{ 
    S() = default; 
    S(const S& other) = delete; 
    S(S&& other) = delete; 
    int i; 
}; 

S nakedBrace() 
{ 
    return {}; // no S constructed here? 
} 

S typedBrace() 
{ 
    return S{}; 
} 

int main() 
{ 
    // produce an observable effect. 
    cout << nakedBrace().i << endl; // ok 
    cout << typedBrace().i << endl; // error: deleted move ctor 
} 

Beispielsitzung:

$ g++ -Wall -std=c++14 -o no-copy-ctor no-copy-ctor.cpp 
no-copy-ctor.cpp: In function 'S typedBrace()': 
no-copy-ctor.cpp:19:12: error: use of deleted function 'S::S(S&&)' 
    return S{}; 
      ^
no-copy-ctor.cpp:8:5: note: declared here 
    S(S&& other) = delete; 

Es erstaunt mich, dass gcc nakedBrace() akzeptiert. Ich dachte, dass die beiden Funktionen konzeptionell gleichwertig sind: Eine temporäre S wird konstruiert und zurückgegeben. Kopieren Elision kann oder darf nicht durchgeführt werden, aber die Verschiebung oder Kopie ctor (beide sind hier gelöscht) muss immer noch zugänglich sein, wie von der Norm (12.8/32) vorgeschrieben.

Bedeutet das, dass nakedBrace() niemals ein S konstruiert? Oder doch, aber direkt im Rückgabewert mit Klammerinitialisierung, so dass konzeptuell keine Kopie move/ctor benötigt wird?

Antwort

4

Dies ist Standardverhalten.

N4140 [stmt.return]/2: [...] eine return-Anweisung mit einer verspannten-init-Liste initialisiert das Objekt oder Referenz von der Funktion, die durch Kopie-list-Initialisierung (8.5.4) aus dem angegebenen Initialisierer zurückgegeben werden Liste. [...]

Dies bedeutet, dass die Initialisierungen durch und typedBrace sind äquivalent zu diesen durchgeführt:

S nakedBrace = {}; //calls default constructor 
S typedBrace = S{}; //calls default, then copy constructor (likely elided) 
1

[stmt.return]/2 ... Eine Rückkehr Eine Anweisung mit einem Ausdruck eines nicht-void-Typs kann nur in Funktionen verwendet werden, die einen Wert zurückgeben. Der Wert des Ausdrucks wird an den Aufrufer der Funktion zurückgegeben. Der Wert des Ausdrucks wird implizit in den Rückgabetyp der Funktion konvertiert, in der er angezeigt wird. Eine return-Anweisung kann das Konstruieren und Kopieren oder Verschieben eines temporären Objekts beinhalten (12.2) ... Eine return-Anweisung mit einer braced-init-Liste initialisiert das Objekt oder die Referenz, die von der Funktion durch Copy-list-initialization zurückgegeben wird (8.5.4) aus der angegebenen Initialisierungsliste.

[class.temporary]/1 Provisorien der Klasse werden in verschiedenen Kontexten erstellt: ... eine prvalue (6.6.3) wiederkehrende ...

Also ja, soweit ich kann sagen, es gibt einen semantischen Unterschied. typedBrace wertet einen Ausdruck S{} aus, der einen Prvalue vom Typ S erzeugt und dann versucht, seinen Rückgabewert von diesem Ausdruck zu kopieren. erstellt stattdessen seinen Rückgabewert direkt aus der braced-init-Liste.

Es ist die gleiche Situation wie mit S s{}; (funktioniert) vs S s = S{}; (funktioniert nicht), nur etwas durch eine Ebene der Indirektion verdeckt.

Verwandte Themen