"Downcasting" Unique_Ptr≪Base≫ to Unique_Ptr≪Derived≫

Downcasting unique_ptr<Base> to unique_ptr<Derived>

I'd create a couple of function templates, static_unique_ptr_cast and dynamic_unique_ptr_cast. Use the former in cases where you're absolutely certain the pointer is actually a Derived *, otherwise use the latter.

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del>
static_unique_ptr_cast( std::unique_ptr<Base, Del>&& p )
{
auto d = static_cast<Derived *>(p.release());
return std::unique_ptr<Derived, Del>(d, std::move(p.get_deleter()));
}

template<typename Derived, typename Base, typename Del>
std::unique_ptr<Derived, Del>
dynamic_unique_ptr_cast( std::unique_ptr<Base, Del>&& p )
{
if(Derived *result = dynamic_cast<Derived *>(p.get())) {
p.release();
return std::unique_ptr<Derived, Del>(result, std::move(p.get_deleter()));
}
return std::unique_ptr<Derived, Del>(nullptr, p.get_deleter());
}

The functions are taking an rvalue reference to ensure that you're not pulling the rug out from underneath the caller's feet by stealing the unique_ptr passed to you.

Downcast unique_ptr to access a function

To do this cast you need to get the actual stored pointer in base[0] like this:

static_cast<Derived*>(bases[0].get())->print()

EDIT:

I agree with @Tietbohl in that dynamic_cast is safer, and that downcasting can be an indicator of bad design. However there are a few situations where, downcasting makes sense, and you can be certain that it is safe.

For example, let's say that you have a factory method, that creates an object with a certain interface, but you provide a parameter indicating that you want a specific concrete class, and then you need to perform operations on the returned object:

Interface* object = factory->CreateObject([parameter specifies type ConcreteA]);

...
static_cast<ConcreteA*>(object)->FuncOnA();

In this situation, you can avoid the complications of RTTI by simply using static_cast.

Why is static downcasting unique_ptr unsafe?

Does it even solve the issue?

You're right, it doesn't (on its own). But that isn't because the default deleter is stateless, but because its implementation is inline.

If not, how can we solve this issue?

We have to make sure that the call to delete happens from the module from which the object was originally allocated (let's call it module A). Since std::default_delete is a template, it is instantiated on-demand and an inline version is called from module B. Not good.


Method 1

One solution is to use a custom deleter all the way through. It doesn't have to be stateful, as long as its implementation resides in module A.

// ModuleA/ModuleADeleter.h

template <class T>
struct ModuleADeleter {
// Not defined here to prevent accidental implicit instantiation from the outside
void operator()(T const *object) const;
};

// Suppose BUILDING_MODULE_A is defined when compiling module A
#ifdef BUILDING_MODULE_A
#define MODULE_A_EXPORT __declspec(dllexport)
#else
#define MODULE_A_EXPORT __declspec(dllimport)
#endif

template class MODULE_A_EXPORT ModuleADeleter<Base>;
template class MODULE_A_EXPORT ModuleADeleter<Derived>;

// ModuleA/ModuleADeleter.cpp

#include "ModuleA/ModuleADeleter.h"

template <class T>
void ModuleADeleter<T>::operator()(T const *object) const {
delete object;
}

template class ModuleADeleter<Base>;
template class ModuleADeleter<Derived>;

(Importing/exporting template instantiations from DLLs is described there).

At this point, we just have to return std::unique_ptr<Base, ModuleADeleter<Base>> from module A, and convert consistently to std::unique_ptr<Derived, ModuleADeleter<Derived>> as needed.

Note that ModuleADeleter<Derived> is only needed if Base has a non-virtual destructor, otherwise just reusing ModuleADeleter<Base> (as the linked answer would) will work as intended.


Method 2

The easiest solution is to used std::shared_ptr instead of std::unique_ptr. It has a bit of a performance penalty, but you don't need to implement and update a deleter, or convert it manually. This works because std::shared_ptr instantiates and type-erases its deleter upon construction, which is done inside module A. This deleter is then stored and kept until needed, and doesn't appear in the pointer's type, so you can mix pointers to objects instantiated from various modules freely.


Also... get_deleter returns a reference to an object lying in the old unique_ptr which is destroyed when this old unique_ptr is destroyed, is it not?

No, get_deleter's return value refers to the deleter contained in the unique_ptr on which you called it. The way the deleter's state is transferred when moving between unique_ptrs is described in overload #6 here.

Transfer ownership of a derived class unique_ptr to its abstract base class unique_ptr

To transfer ownership of a derived class managed by a derived class unique_ptr to a base class unique_ptr, you can (and should) use move semantics.

    std::unique_ptr<Derived> foo = std::make_unique<Derived>();
std::unique_ptr<Base> bar = std::move(foo);

To return ownership to a derived unique_ptr, you need to get a little messier:

    std::unique_ptr<Derived> foo = std::make_unique<Derived>();
std::unique_ptr<Base> bar = std::move(foo);

std::unique_ptr<Derived> biz(static_cast<Derived*>(bar.release()));

If you're unsure of the actual type of the pointer, then a dynamic cast can be used to check that it's correct. Note that we use std::unique_ptr<Base>::get() in the conditional, since we're not sure we want to release ownership yet. If this passes, then we can call std::unique_ptr<Base>::release().

    std::unique_ptr<Derived> foo = std::make_unique<Derived>();
std::unique_ptr<Base> bar = std::move(foo);

// dynamic cast if we're unsure that it is castable
if (dynamic_cast<Derived*>(bar.get())) {
foo.reset(static_cast<Derived*>(bar.release()));
}

see it in action

Is unique_ptr<Derived> to unique_ptr<Base> up-casting automatic?

The (draft) standard says:

// 20.8.1.2.1, constructors
...
template <class U, class E>
unique_ptr(unique_ptr<U, E>&& u) noexcept;
template <class U>
unique_ptr(auto_ptr<U>&& u) noexcept;

Those are constructors from any unique_ptr. The standard further restricts their usage by clauses like this:

24 Remarks: This constructor shall not participate in overload resolution unless U* is implicitly convertible to T* and D is the same type as default_delete<T>

The effect of this remark is that unique_ptr<T> is constructible from unique_ptr<U> precisely U* is convertible to T* (and all deleter requirements are met). In particular, when T is an unambiguous public base class of U.

Since the constructor is not explicit, it serves as an implicit converter from unique_ptr<U> to unique_ptr<T>.

std::unique_ptr with derived class

If they are polymorphic types and you only need a pointer to the derived type use dynamic_cast:

Derived *derivedPointer = dynamic_cast<Derived*>(basePointer.get());

If they are not polymorphic types only need a pointer to the derived type use static_cast and hope for the best:

Derived *derivedPointer = static_cast<Derived*>(basePointer.get());

If you need to convert a unique_ptr containing a polymorphic type:

Derived *tmp = dynamic_cast<Derived*>(basePointer.get());
std::unique_ptr<Derived> derivedPointer;
if(tmp != nullptr)
{
basePointer.release();
derivedPointer.reset(tmp);
}

If you need to convert unique_ptr containing a non-polymorphic type:

std::unique_ptr<Derived>
derivedPointer(static_cast<Derived*>(basePointer.release()));


Related Topics



Leave a reply



Submit