44

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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • 2
    `static_pointer_cast` is only defined for argument of type `std::shared_ptr` - it is not usable at all with `unique_ptr` – M.M Mar 20 '16 at 22:42
  • 1
    @M.M Yes, I know, I wrote almost the same in the question saying that does not exist an equivalent for the `unique_ptr`. – skypjack Mar 20 '16 at 22:51
  • Why does type-erasure lead to downcasting? If you need to cast your base-class into a derived type, it is most often, except for very few cases, a design issue which should be solved differently. – Jens Mar 21 '16 at 09:14
  • @Jens Imagine `struct B { virtual void f() = 0; }; template struct D: public B { void f() override { /* do something with D */ } };`, have you ever done something like that? With the good mix of wrappers and `static_pointer_cast` you can create a lot of interesting things. – skypjack Mar 21 '16 at 09:35
  • @skypjack I have done mix-ins and CRTP before, These are probably two of the few cases where casting makes sense, but they don't involve `unique_ptr`s. In you example you have a factory which returns a `unique_ptr`, and then you to cast it to access derived-class functionality. That is usually a design-issue. – Jens Mar 21 '16 at 09:57
  • @Jens My example does not involve neither CRTP nor mixin, but thanks for your suggestion. – skypjack Mar 21 '16 at 10:08
  • Hi, how about creating a custom `MyUniquePtr` that can `MyUniquePtr foo=std::move(MyUniquePtr)` automatically? – javaLover Apr 12 '17 at 07:19
  • @javaLover Can you provide a snippet to clarify your comment? – skypjack Apr 12 '17 at 07:22
  • http://coliru.stacked-crooked.com/a/cb27300bea5d10a8 - A prototype – javaLover Apr 12 '17 at 07:40
  • @javaLover Oh, ok, got it, thank you. Feel free to add one more answer if you want. – skypjack Apr 12 '17 at 07:41
  • @skypjack Actually, I am kindly asking you for a brief review. : Except that the class is still incomplete, do you think this is a bad idea in the first place? I am going to use it to replace all crappy std::unique_ptr. Thank! – javaLover Apr 12 '17 at 07:42
  • 1
    @javaLover Well, I'd say that the other answers have a more direct and clean approach, but... it works... that's it. :-) – skypjack Apr 12 '17 at 07:44
  • @javaLover the problem with your type is that it *only* does one kind of cast (a static cast), whereas the free function `*_unique_cast` templates can be written for all four kinds of cast – Caleth May 05 '17 at 10:41
  • The advice in this question also helped me figure out how to `const_pointer_cast` a `unique_ptr`. It stinks that you really can't, because I had to change a bunch of functions to use a pointer or `const` pointer to the base type instead of a reference or `const` reference to a `unique_ptr`. The references ensured the object was still in scope and owned. :( Maybe I'll see if I can use a reference to the base type instead? That's almost as good... – Keith M Dec 13 '18 at 02:05

3 Answers3

55

#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
Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
Anedar
  • 4,235
  • 1
  • 23
  • 41
  • 2
    Why not reverse the order of those template parameters and let `FROM` be deduced, just like in `std::static_pointer_cast`? – aschepler Mar 20 '16 at 22:52
  • @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. – skypjack Mar 20 '16 at 22:54
  • 2
    @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. – Anedar Mar 20 '16 at 23:00
  • 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_ptr`s, of course... :-) – skypjack Mar 20 '16 at 23:11
  • 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. – Anedar Mar 20 '16 at 23:17
  • @HowardHinnant because the constructor is marked explicit, i fixed it. See [here](http://ideone.com/ixq9kM) – Anedar Mar 21 '16 at 00:22
7

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.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 1
    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 – skypjack Mar 20 '16 at 23:03
  • 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. – Jonathan Wakely Mar 20 '16 at 23:05
  • 1
    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 Mar 20 '16 at 23:07
  • 3
    `std::exchange` is easy to write and nobody uses it. How did it get in? :-) – Howard Hinnant Mar 21 '16 at 00:13
  • 1
    @HowardHinnant I think `std::exchange` works great as [a duck](http://blog.codinghorror.com/new-programming-jargon/). – Barry Mar 21 '16 at 02:21
1

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()));
}
Community
  • 1
  • 1
Matthias
  • 4,481
  • 12
  • 45
  • 84