2016-03-10 4 views
15

Ich bin es gewohnt, nicht std::move bei der Rückgabe einer std::unique_ptr zu verwenden, weil dies RVO verhindert. Ich habe diesen Fall, wo ich einen lokalen std::unique_ptr habe, aber der Rückgabetyp ist ein std::shared_ptr. Hier ist ein Beispiel für den Code:Rückgabe lokale unique_ptr als shared_ptr

shared_ptr<int> getInt1() { 
    auto i = make_unique<int>(); 

    *i = 1; 

    return i; 
} 

shared_ptr<int> getInt2() { 
    return make_unique<int>(2); 
} 

unique_ptr<int> getInt3() { 
    auto ptr = make_unique<int>(2); 

    return ptr; 
} 

int main() { 
    cout << *getInt1() << endl << *getInt2() << *getInt3() << endl; 
    return 0; 
} 

GCC beide Fälle akzeptiert, aber Clang weigert sich, die getInt1() Mit diesem Fehler:

main.cpp:10:13: error: no viable conversion from 'std::unique_ptr<int, std::default_delete<int> >' to 'shared_ptr<int>' 
    return i; 
     ^

Hier beide Fälle auf coliru: GCC, Clang

Both Compiler akzeptiert den dritten Fall.

Welcher ist falsch? Vielen Dank.

+2

Ich vermute, dass wir in der Lage sein werden, bessere Antworten zu geben, wenn wir verstehen, wofür Sie das brauchen. Im Moment könnte das trivial gelöst werden, indem make_shared statt make_unique aufgerufen wird. Ich nehme an, dass es einen Grund gibt, warum das nicht möglich ist, und es würde helfen, diesen Grund zu verstehen. – Elliott

+7

@Eliott: Was könnte gelöst werden? Er fragt nicht, wie er etwas lösen soll, er fragt, ob der Code, den er hat, dem Standard entspricht. –

Antwort

17

Die richtige Antwort hängt davon ab, über welchen C++ - Standard Sie sprechen.

Wenn wir über C++ 11 sprechen, ist das Klängen korrekt (eine explizite Verschiebung ist erforderlich). Wenn wir über C++ 14 sprechen, ist gcc korrekt (eine explizite Verschiebung ist nicht erforderlich).

C++ 11 sagt in N3290/[class.copy]/p32:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, ...

Dies erfordert, dass Sie nur die implizite Bewegung bekommen, wenn die Rückkehr Ausdruck den gleichen Typ wie die Funktion Rückgabetyp hat.

Aber CWG 1579 änderte dies, und dieser Fehlerbericht wurde nach C++ 11 und rechtzeitig für C++ 14 akzeptiert. Das gleiche Absatz lautet nun:

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, ...

Diese Modifikation erlaubt grundsätzlich die Rückkehr Ausdrucksart umwandelbar sein, um die Funktion Rückgabetyp und nach wie vor für die implizite Bewegung in Betracht.

Bedeutet dies, dass der Code eine #if/#else basierend auf dem Wert __cplusplus braucht?

Man könnte das tun, aber ich würde nicht stören.Wenn ich zielte C++ 14, würde ich nur:

return i; 

Wenn der Code unerwartet unter einem C++ 11-Compiler ausgeführt wird, werden Sie zum Zeitpunkt der Kompilierung des Fehlers mitgeteilt werden, und es ist trivial zu beheben:

return std::move(i); 

Wenn Sie gerade Targeting 11 C++, die move verwenden.

Wenn Sie sowohl C++ 11 als auch C++ 14 (und darüber hinaus) als Ziel verwenden möchten, verwenden Sie move. Der Nachteil der Verwendung von move ist, dass Sie RVO (Return Value Optimization) verhindern können. In diesem Fall ist RVO jedoch nicht einmal legal (wegen der Umwandlung von der return-Anweisung in den Rückgabetyp der Funktion). Und so tut die kostenlose move nichts weh.

Das ein Mal, wenn Sie zu einer unentgeltlichen move auch anlehnen könnten, wenn C++ 14-Targeting ist, wenn ohne sie noch Dinge kompilieren in C++ 11 und rufen eine teure Kopie Umwandlung, wie zu einer Bewegung gegen Umwandlung. In diesem Fall würde das versehentliche Kompilieren unter C++ 11 einen stillen Leistungsfehler verursachen. Und wenn sie unter C++ 14 kompiliert wird, hat die kostenlose move immer noch keine nachteiligen Auswirkungen.

+0

@TobySpeight: Addressed, danke. –

+0

Wenn Sie nur C++ 11 verwenden, würde ich ermutigen, den richtigen Typ vor dem Zurückgeben zu erstellen, anstatt "std :: move (..." in Ihrem Code zu haben. Denn dann könnte der Compiler einfach NRVO tun, anstatt a zu verschieben einige Male. – Daemin

9

std::unique_ptr könnte verwendet werden, nur zu konstruieren std::shared_ptr, wenn es sich um ein R-Wert ist. Siehe die Konstruktor Erklärung std::shared_ptr:

template< class Y, class Deleter > 
shared_ptr(std::unique_ptr<Y,Deleter>&& r); 

So müssen Sie std::move verwenden, um den ersten Fall arbeiten zu lassen, sonst sollte es scheitern.

return std::move(i); 

BTW: Ich habe den Code mit gcc 4.9.3 kompiliert es entweder fehlgeschlagen.

source_file.cpp:14:12: error: cannot bind ‘std::unique_ptr<int, std::default_delete<int> >’ 
lvalue to ‘std::unique_ptr<int, std::default_delete<int> >&&’ 
    return i; 
      ^
+0

Hallo, könnten Sie Ihre Antwort ändern, um anzugeben, dass die Verschiebung nur für 'C++ 11' benötigt wird? –

+0

Dies könnte verwandt sein http://stackoverflow.com/q/18889843/985296 – stefan