Alternatives of static_pointer_cast for unique_ptr

36,638

Solution 1

#Raw pointers

The solution for your problem is to get the raw (non-owning) pointer and cast it - then just let the raw pointer go out of scope and let the remaining unique_ptr<Base> control the lifetime of the owned object.

Like this:

unique_ptr<Base> foo = fooFactory();

{
    Base* tempBase = foo.get();
    Derived* tempDerived = static_cast<Derived*>(tempBase);
} // tempBase and tempDerived go out of scope here, but foo remains -> no need to delete

#Unique_pointer_cast The other option is to use the release() function of unique_ptr to wrap it into another unique_ptr.

Like this:

template<typename TO, typename FROM>
unique_ptr<TO> static_unique_pointer_cast (unique_ptr<FROM>&& old){
    return unique_ptr<TO>{static_cast<TO*>(old.release())};
    // conversion: unique_ptr<FROM>->FROM*->TO*->unique_ptr<TO>
}

unique_ptr<Base> foo = fooFactory();

unique_ptr<Derived> foo2 = static_unique_pointer_cast<Derived>(std::move(foo));

Remember that this invalidates the old pointer foo

#Reference from raw pointers Just for completeness of the answer, this solution was actually proposed as a small modification of the raw pointers by the OP in the comments.

Similar to using raw pointers one can cast the raw pointers and then create a reference out of them by derefering. In this case it is important to guarantee that the lifetime of the created reference does not exceed the lifetime of the unique_ptr.

Sample:

unique_ptr<Base> foo = fooFactory();
Derived& bar = *(static_cast<Derived*>(foo.get()));
// do not use bar after foo goes out of scope

Solution 2

I understand that using static_pointer_cast with unique_ptr would lead to a shared ownership of the contained data.

Only if you define it badly. The obvious solution would be for it to transfer ownership, so that the source object ends up empty.

If you don't want to transfer ownership then just use a raw pointer.

Or if you want two owners then use shared_ptr.

It seems like your question is only partly about the actual cast operation, and partly just lack of a clear ownership policy for the pointer. If you need multiple owners, whether they both use the same type, or whether one is cast to a different type, then you should not be using unique_ptr.

Anyway doing that results with two unique_ptr that should never exist at the same time, so it is simply forbidden.
Right, it makes sense, absolutely, that's why there doesn't exist anything like static_unique_pointer_cast indeed.

No, that's not why it doesn't exist. It doesn't exist because it's trivial to write it yourself, if you need it (and as long as you give it sane semantics of unique ownership). Just get the pointer out with release() cast it, and put it in another unique_ptr. Simple and safe.

That isn't the case for the shared_ptr, where the "obvious" solution doesn't do the right thing:

shared_ptr<Derived> p2(static_cast<Derived*>(p1.get());

That would create two different shared_ptr objects that own the same pointer, but don't share ownership (i.e. they would both try to delete it, causing undefined behaviour).

When shared_ptr was first standardized there was no safe way to do that, so static_pointer_cast and the related casting functions were defined. They needed access to the implementation details of the shared_ptr bookkeeping info to work.

However, during the C++11 standardization process shared_ptr was enhanced by the addition of the "aliasing constructor" which allows you to do the cast simply and safely:

shared_ptr<Derived> p2(p1, static_cast<Derived*>(p1.get());

If this feature had always been part of shared_ptr then it's possibly, maybe even likely, that static_pointer_cast would never have been defined.

Solution 3

I would like to add something to the previous answer of Anedar which calls the release() member method of the given std::unique_ptr< U >. If one wants to implement a dynamic_pointer_cast as well (in addition to a static_pointer_cast) for converting std::unique_ptr< U > to std::unique_ptr< T >, one must ensure the resource guarded by the unique pointer is released properly in case the dynamic_cast fails (i.e. returns a nullptr). Otherwise, a memory leak occurs.

Code:

#include <iostream>
#include <memory>

template< typename T, typename U >
inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) {
    U * const stored_ptr = ptr.release();
    T * const converted_stored_ptr = dynamic_cast< T * >(stored_ptr);
    if (converted_stored_ptr) {
        std::cout << "Cast did succeeded\n";
        return std::unique_ptr< T >(converted_stored_ptr);
    }
    else {
        std::cout << "Cast did not succeeded\n";
        ptr.reset(stored_ptr);
        return std::unique_ptr< T >();
    }
}

struct A { 
    virtual ~A() = default;
};
struct B : A {
    virtual ~B() { 
        std::cout << "B::~B\n"; 
    }
};
struct C : A {
    virtual ~C() { 
        std::cout << "C::~C\n"; 
    }
};
struct D { 
    virtual ~D() { 
        std::cout << "D::~D\n"; 
    }
};

int main() {

  std::unique_ptr< A > b(new B);
  std::unique_ptr< A > c(new C);
  std::unique_ptr< D > d(new D);

  std::unique_ptr< B > b1 = dynamic_pointer_cast< B, A >(std::move(b));
  std::unique_ptr< B > b2 = dynamic_pointer_cast< B, A >(std::move(c));
  std::unique_ptr< B > b3 = dynamic_pointer_cast< B, D >(std::move(d));
}

Output (possible ordering):

Cast did succeeded
Cast did not succeeded
Cast did not succeeded
B::~B
D::~D
C::~C

The destructors of C and D will not be called, if one uses:

template< typename T, typename U >
inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) {
    return std::unique_ptr< T >(dynamic_cast< T * >(ptr.release()));
}
Share:
36,638
skypjack
Author by

skypjack

Give a man a game engine and he delivers a game. Teach a man to make a game engine and he never delivers anything.

Updated on January 20, 2022

Comments

  • skypjack
    skypjack over 2 years

    I understand that using static_pointer_cast with unique_ptr would lead to a shared ownership of the contained data.
    In other terms, what I'd like to do is:

    unique_ptr<Base> foo = fooFactory();
    // do something for a while
    unique_ptr<Derived> bar = static_unique_pointer_cast<Derived>(foo);
    

    Anyway doing that results with two unique_ptr that should never exist at the same time, so it is simply forbidden.
    Right, it makes sense, absolutely, that's why there doesn't exist anything like static_unique_pointer_cast indeed.

    So far, in cases where I want to store pointers to those base classes, but I also need to cast them to some derived classes (as an example, imagine a scenario involving type erasure), I've used shared_ptrs because of what I've above mentioned.

    Anyway, I was guessing if there are alternatives to shared_ptrs for such a problem or if they are really the best solution in that case.

  • aschepler
    aschepler about 8 years
    Why not reverse the order of those template parameters and let FROM be deduced, just like in std::static_pointer_cast?
  • skypjack
    skypjack about 8 years
    @Anedar Yeah, obviously I don't want to deal with raw pointers, but you gave me a good hint anyway... The implementation of static_unique_pointer_cast is interesting, but unfortunately does not fit well with the idea (expressed in the question) of storing those pointers, because once they have been invalidated they must be re-initialized for future uses after the current use, so it would lead to a huge amount of work around a container.
  • Anedar
    Anedar about 8 years
    @skypjack the question that arises is this: who is the owner of the object pointed to (namely the one responsible for deletion)? Only a single pointer at a time? -> Use a unique_ptr, from which there might be only a single one at a time. Multiple ones (of possibly different classes)? -> Use shared pointers. For pointers that are not responsible for deletion, use raw pointers. And if you decide to have a single owner, they are probably the only way to get pointers of different classes to the same object while keeping ownership at the original object.
  • skypjack
    skypjack about 8 years
    Well, It doesn't exist because it's trivial to write it yourself could be applied also to some other parts (that exist) of the STL indeed, but I got the point of your answer. +1
  • Jonathan Wakely
    Jonathan Wakely about 8 years
    You missed the end of the sentence, "if you need it". Things are worth putting in the standard if they are hard/impossible for users to write correctly (which was true for shared_ptr casts originally) or if they are easy but everyone keeps reimplementing it. Neither is true for unique_ptr casts, because it's easy, but not often needed.
  • skypjack
    skypjack about 8 years
    I agree, honestly I guess that I'm facing an XY-problem, but still the question was interesting to me to know what others would have done if they were me.
  • skypjack
    skypjack about 8 years
    Well, not so easy, it's a set of pointers to type-erased classes that are casted forward once needed, so the owner is one, but still I can incur in problems as if they were two. Anyway, it's a matter of getting the raw pointer with get, cast it and derefer it, in order to return a reference for which a can guarantee for the lifetime. Otherwise I can also have a go with shared_ptrs, of course... :-)
  • Anedar
    Anedar about 8 years
    ok, thats basically using raw pointers but with an additional derefer to keep you from having to deal with pointer-logic afterwards. Sounds like a good solution, since you said you can guarantee for the lifetime of the reference to not exceed the lifetime of the unique_ptr.
  • Howard Hinnant
    Howard Hinnant about 8 years
    std::exchange is easy to write and nobody uses it. How did it get in? :-)
  • Anedar
    Anedar about 8 years
    @HowardHinnant because the constructor is marked explicit, i fixed it. See here
  • Barry
    Barry about 8 years
    @HowardHinnant I think std::exchange works great as a duck.