2016-03-10 9 views
15

Jestem przyzwyczajony do nieużywania std::move podczas zwracania std::unique_ptr, ponieważ w ten sposób zabrania się RVO. Mam ten przypadek, w którym mam lokalny std::unique_ptr, ale typem zwrotu jest std::shared_ptr. Oto przykładowy kod:Powracający lokalny unique_ptr jako 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 akceptuje oba przypadki, ale Clang odmawia getInt1() z tym błędem:

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

Oto obu przypadkach w coliru: GCC, Clang

Both kompilator zaakceptuje trzeci przypadek.

Który z nich jest nieprawidłowy? Dzięki.

+2

Podejrzewam, że będziemy w stanie udzielić lepszych odpowiedzi, gdy zrozumiemy, czego potrzebujesz. W tej chwili można to rozwiązać banalnie, wywołując make_shared zamiast make_unique. Zakładam, że jest jakiś powód, dla którego nie jest to możliwe i pomoże to zrozumieć. – Elliott

+7

@ Elliott: Co można rozwiązać? Nie pyta, jak rozwiązać cokolwiek, pyta, czy kod, który ma, jest poprawny według standardu. –

Odpowiedz

17

Prawidłowa odpowiedź zależy od standardu C++, o którym mówisz.

Jeśli mówimy o C++ 11, klang jest poprawny (wymagany jest wyraźny ruch). Jeśli mówimy o C++ 14, gcc jest poprawne (wyraźny ruch nie jest potrzebny).

C++ 11 mówi w 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, ...

Wymaga to, że można dostać tylko niejawny ruch powrotny, gdy wyrażenie ma ten sam typ jako typ zwracanej przez funkcję.

Ale zmieniono to CWG 1579, a ten raport o usterce został przyjęty po C++ 11, a w czasie dla C++ 14. Ten sam ustęp brzmi teraz:

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, ...

Modyfikacja ta pozwala w zasadzie rodzaj ekspresji powrót być zamienny do typ zwracanej przez funkcję i nadal kwalifikować się do utajonego ruchu.

Czy to oznacza, że ​​kod wymaga #if/#else w oparciu o wartość __cplusplus?

Można to zrobić, ale nie zawracałbym sobie głowy.Gdybym kierowania C++ 14, ja po prostu:

return i; 

Jeśli kod jest nieoczekiwanie pracował w C++ 11 kompilatora, użytkownik zostanie powiadomiony w czasie kompilacji błędu, a to jest trywialne naprawić:

return std::move(i); 

Jeśli dopiero kierowania C++ 11, użyj move.

Jeśli chcesz kierować zarówno C++ 11, jak i C++ 14 (i nie tylko), użyj move. Wadą korzystania z usługi move jest nieuzasadniona możliwość zablokowania RVO (Optymalizacja wartości zwracanej). Jednak w tym przypadku RVO nie jest nawet legalne (z powodu konwersji z instrukcji return na typ zwracany przez funkcję). I tak darmowy move nic nie boli.

Ten czas można pochylić w kierunku nieodpłatnego move nawet podczas kierowania C++ 14 jest czy bez niego, sytuacja wciąż kompilacji w C++ 11 i powołać drogiego kopia konwersji, w przeciwieństwie do ruchu Konwersja. W tym przypadku przypadkowe skompilowanie pod C++ 11 wprowadziłoby cichy błąd wydajności. A gdy kompilowany pod C++ 14 darmowy move nadal nie ma szkodliwych skutków.

+0

@TobySpeight: Adresowany, dziękuję. –

+0

Jeśli używasz tylko C++ 11, zachęcam do stworzenia poprawnego typu przed powrotem, zamiast "powrotu std :: move (..." w kodzie, ponieważ wtedy kompilator mógłby po prostu wykonać NRVO zamiast przesuwać kilka razy. – Daemin

9

std::unique_ptr może być użyty do skonstruowania std::shared_ptr tylko wtedy, gdy jest to wartość r. Zobacz deklarację konstruktora std::shared_ptr:

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

Więc trzeba użyć std::move aby 1st dzieło przypadku, w przeciwnym razie należy go zawieść.

return std::move(i); 

BTW: I skompilowany kod z gcc 4.9.3 nie udało albo.

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

Witam, czy mógłbyś zmienić swoją odpowiedź, aby określić, że ruch jest potrzebny tylko dla 'C++ 11'? –

+0

Może to być powiązane http://stackoverflow.com/q/18889843/985296 – stefan