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()));
}
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, 2022Comments
-
skypjack over 2 years
I understand that using
static_pointer_cast
withunique_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 likestatic_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. -
aschepler about 8 yearsWhy not reverse the order of those template parameters and let
FROM
be deduced, just like instd::static_pointer_cast
? -
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 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 about 8 yearsWell, 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 about 8 yearsYou 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 about 8 yearsI 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 about 8 yearsWell, 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 withshared_ptr
s, of course... :-) -
Anedar about 8 yearsok, 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 about 8 years
std::exchange
is easy to write and nobody uses it. How did it get in? :-) -
Anedar about 8 years@HowardHinnant because the constructor is marked explicit, i fixed it. See here
-
Barry about 8 years@HowardHinnant I think
std::exchange
works great as a duck.