2015-11-28 33 views
13

Powiedzmy mam następującą hierarchię klas:Prawidłowy sposób powrocie std :: unique_ptr do obiektu polimorficznych klasy

struct Base 
{ 
}; 

struct Derived : public Base 
{ 
    void DoStuffSpecificToDerivedClass() 
    { 
    } 
}; 

oraz następujące metody fabryki:

std::unique_ptr<Base> factoryMethod() 
{ 
    auto derived = std::make_unique<Derived>(); 
    derived->DoStuffSpecificToDerivedClass(); 
    return derived; // does not compile 
} 

Problem jest, instrukcja return nie kompiluje się, ponieważ std::unique_ptr nie ma konstruktora kopii z obsługą kowariancji (co ma sens, ponieważ nie ma żadnych konstruktorów kopiowania), ma tylko konstruktor ruchu z obsługą kowariancji.

Jaki jest najlepszy sposób rozwiązania tego problemu? Myślę można na dwa sposoby:

return std::move(derived); // this compiles 
return std::unique_ptr<Base>(derived.release()); // and this compiles too 

EDIT 1: Używam Visual C++ 2013 jako mojego kompilatora. Oryginalny komunikat o błędzie dla return derived wygląda następująco:

Error 1 error C2664: 'std::unique_ptr<Base,std::default_delete<_Ty>>::unique_ptr(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)' : cannot convert argument 1 from 'std::unique_ptr<Derived,std::default_delete<Derived>>' to 'std::unique_ptr<Derived,std::default_delete<Derived>> &&' 

EDIT 2: Jest to świeżo utworzona aplikacja konsoli ze standardowej VS 2013 szablonu. Nie zmieniłem ustawień kompilatora. wiersz poleceń kompilator wygląda następująco:

Debug:

/Yu"stdafx.h" /GS /analyze- /W3 /Zc:wchar_t /ZI /Gm /Od /sdl /Fd"Debug\vc120.pdb" /fp:precise /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /Oy- /MDd /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\CppApplication1.pch" 

Release:

/Yu"stdafx.h" /GS /GL /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /Fa"Release\" /EHsc /nologo /Fo"Release\" /Fp"Release\CppApplication1.pch" 
+0

Kompiluje się dobrze z VC++ 2013. Opublikuj MCVE i powiedz nam, jak wywołujesz kompilator. –

+0

@ChristianHackl Dodałem informację o linii poleceń kompilatora do mojego pytania. Nawiasem mówiąc, C++ Shell też się nie kompiluje: http://cpp.sh/8lxwn –

+0

Wciąż nie ma MCVE. http://stackoverflow.com/help/mcve –

Odpowiedz

8

Można to zrobić:

return std::move(derived); 

ten sposób poinformować kompilator nie kopia jest potrzebne, co spełnia wymagania unique_ptr. Jeśli typy są idealnie dopasowane, nie powinieneś wyraźnie określać move, ale w tym przypadku robisz to.

+0

OP jest już świadomy tego rozwiązania. I najwyraźniej to nie działa dla niego. To wszystko w tym pytaniu. –

+0

"Jeśli typy są idealnie dopasowane, nie powinieneś wyraźnie określać ruchu, ale w tym przypadku robisz" - to odpowiada na moje pytanie. Nie wiedziałem, że niejawny ruch występuje tylko wtedy, gdy typy pasują idealnie. Dzięki. –

5

Jak stwierdzono w pytaniu, problem polega na tym, że instrukcja return nie kompiluje się, std::unique_ptr nie ma konstruktora kopii z obsługą kowariancji, ma tylko konstruktor ruchu z obsługą kowariancji, jednak kompilator wciąż się nie porusza od std::unique_ptr<Derived>.

Dzieje się tak dlatego, że warunki przejścia z obiektu zwróconego z funkcji są ściśle związane z kryteriami kopiowania, co bezwzględnie wymaga, aby typ zwracanego obiektu musiał być taki sam jak typ zwracany przez funkcję.

[class.copy]/32:

Kiedy kryteria elizji operacji kopiowania są spełnione lub byłoby spełnione za wyjątkiem faktu, że obiekt źródłowy jest parametr funkcji, a Obiekt do skopiowania jest oznaczony przez l-wartość, przeciążenie Rozdzielczość, aby wybrać konstruktora dla kopii jest najpierw wykonywana , tak jakby obiekt został wyznaczony przez wartość r.

Dlatego wolę,

return std::move(derived); 

Jednak jest zmiana reguł w DR-9R5 tak że wartość zwracana będzie traktowany jako rvalue nawet gdy typy nie są takie same, gcc-5 realizowane reguła i nie trzeba zmieniać kodu dla gcc-5, jak pokazano here.