Alternatives of static_pointer_cast for unique_ptr
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()));
}