Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The correct way of returning std::unique_ptr to an object of polymorphic class

Let's say I have the following hierarchy of classes:

struct Base 
{
};

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

And the following factory method:

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

The problem is, the return statement does not compile, because std::unique_ptr does not have a copy constructor with covariance support (which makes sense since it does not have any copy constructors), it only has a move constructor with covariance support.

What is the best way to make solve this problem? I can think of two ways:

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

EDIT 1: I'm using Visual C++ 2013 as my compiler. The original error message for return derived looks like this:

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: It is a freshly created console app from a standard VS 2013 template. I haven't tweaked any compiler settings. Compiler command line looks like this:

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" 
like image 718
RX_DID_RX Avatar asked Nov 28 '15 12:11

RX_DID_RX


People also ask

What is std :: unique_ptr?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

What happens when you return a unique_ptr?

If a function returns a std::unique_ptr<> , that means the caller takes ownership of the returned object. class Base { ... }; class Derived : public Base { ... }; // Foo takes ownership of |base|, and the caller takes ownership of the returned // object.

What does the get () function of the unique_ptr class do?

std::unique_ptr::getReturns the stored pointer. The stored pointer points to the object managed by the unique_ptr, if any, or to nullptr if the unique_ptr is empty.

What happens when you move a unique_ptr?

A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it.


2 Answers

You can do this:

return std::move(derived);

That way you tell the compiler no copy is needed, which satisfies the requirements of unique_ptr. If the types matched perfectly you should not need to explicitly specify move, but in this case you do.

like image 145
John Zwinck Avatar answered Sep 20 '22 22:09

John Zwinck


As stated in the question, the problem is, the return statement does not compile, std::unique_ptr does not have a copy constructor with covariance support, it only has a move constructor with covariance support, however, compiler still doesn't move from std::unique_ptr<Derived>.

It is because conditions for moving from an object returned from a function are tied closely to the criteria for copy elision, which strictly requires that type of the object being returned need to be same as the return type of the function.

[class.copy]/32:

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.

Therefore, I prefer,

return std::move(derived);

However, there is rule change in DR-9R5 so that the return value will be treated as an rvalue even when the types are not the same, gcc-5 implemented the rule and you don't need to change your code for gcc-5 as shown here.

like image 36
Alper Avatar answered Sep 21 '22 22:09

Alper