2016-09-30 7 views
13

Ich versuche, ein std::unique_ptr class Mitglied (versucht, den Besitz zu verschieben) an den Aufrufer zurückgeben. Das folgende ist ein Beispielcode-Schnipsel:Returning-Member unique_ptr von der Klassenmethode

class A { 
public: 
    A() : p {new int{10}} {} 

    static std::unique_ptr<int> Foo(A &a) { 
    return a.p; // ERROR: Copy constructor getting invoked 
       // return std::move(a.p); WORKS FINE 
    } 

    std::unique_ptr<int> p; 
}; 

Ich dachte, den Compiler (gcc-5.2.1) wäre in der Lage Rückgabewert Optimierung (Kopie elision) in diesem Fall zu tun, ohne die ausdrückliche Absicht über std::move() zu erfordern. Aber das ist nicht der Fall. Warum nicht?

Der folgende Code scheint zu funktionieren, die äquivalent scheint:

std::unique_ptr<int> foo() { 
    std::unique_ptr<int> p {new int{10}}; 
    return p; 
} 
+2

Dies ist eine gute erste Frage. Willkommen bei StackOverflow! – Barry

Antwort

11

Die Regel in [class.copy] ist:

[...], wenn die Ausdruck in einer return Aussage ist ein (möglicherweise geklammert) ID-Ausdruck, die ein Objekt mit automatischer Speicherdauer im Körper deklariert oder Parameter-deklarieren Ration-Klausel der innersten umschließenden Funktion oder Lambda-Ausdruck, Überladungsauflösung zu wählen Sie den Konstruktor für die Kopie wird zuerst so durchgeführt, als ob das Objekt von einem Rvalue bezeichnet wurden.

In diesem Beispiel:

std::unique_ptr<int> foo() { 
    std::unique_ptr<int> p {new int{10}}; 
    return p; 
} 

p ist der Name eines Objekts mit automatischer Speicherdauer in dem Körper der Funktion erklärt. Anstatt es in den Rückgabewert zu kopieren, versuchen wir zuerst, es zu verschieben. Das funktioniert gut.

Aber in diesem Beispiel:

static std::unique_ptr<int> Foo(A &a) { 
    return a.p; 
} 

das nicht gelten. a.p ist nicht der Name eines Objekts, also versuchen wir nicht die Überladungsauflösung, als wäre es ein rvalue, sondern wir machen nur das normale: versuchen Sie es zu kopieren. Das scheitert, also musst du es explizit move() it.


Das ist der Wortlaut der Regel, aber es könnte Ihre Frage nicht beantworten. Warum ist das die Regel? Grundsätzlich - wir versuchen, in Sicherheit zu sein. Wenn wir eine lokale Variable benennen, ist es immer sicher, in einer return-Anweisung davon abzukommen. Es wird nie wieder zugegriffen werden. Einfache Optimierung, kein möglicher Nachteil. Aber in Ihrem ursprünglichen Beispiel ist a nicht von dieser Funktion gehört, noch ist a.p. Es ist nicht von Natur aus sicher, sich davon zu entfernen, daher versucht die Sprache nicht, dies automatisch zu tun.

-1

Kopieren Elision kann (unter anderem) nicht gelten, da a.p eine std::unique_ptr ist, die nicht kopierbar ist. Und da a.p eine Lebensdauer jenseits des Körpers von A::Foo(A&) hat, wäre es sehr überraschend (wie in, überraschend für die Person, die den Code schreibt), wenn der Compiler automatisch versucht, sich von a.p zu bewegen, was wahrscheinlich die Klasseninvarianten von a zerstören würde. Es würde funktionieren, wenn Sie return std::move(a.p);, aber das stiehlt explizit a.p.

+0

Kopieren Elision kann auf Nur-Verschieben-Typen angewendet werden (wie das zweite Beispiel von OP) – Barry

+0

@Barry OK, mein erster Satz ist falsch. Kopieren Elision gilt auch für Züge. Ich glaube jedoch, dass der Rest der Antwort immer noch steht. –

+0

@AndreKostur: Da der Coder explizit versucht, ein unique_ptr zurückzugeben, dessen Kopierkonstruktor gelöscht wird, besteht die einzige Option darin, dass eine Verschiebung stattfindet und die Kopie sollte funktionieren (IMO). Was mich überraschte, war eher der Kompilierungsfehler als eine Warnung. – axg