简体   繁体   中英

Alternatives of static_pointer_cast for unique_ptr

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_ptr s because of what I've above mentioned.

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

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> constrol 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

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 (ie 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.

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 (ie 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()));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM