56

As it was the case in Boost, C++11 provides some functions for casting shared_ptr:

std::static_pointer_cast
std::dynamic_pointer_cast
std::const_pointer_cast

I am wondering, however, why there are no equivalents functions for unique_ptr.

Consider the following simple example:

class A { virtual ~A(); ... }
class B : public A { ... }

unique_ptr<A> pA(new B(...));

unique_ptr<A> qA = std::move(pA); // This is legal since there is no casting
unique_ptr<B> pB = std::move(pA); // This is not legal

// I would like to do something like:
// (Of course, it is not valid, but that would be the idea)
unique_ptr<B> pB = std::move(std::dynamic_pointer_cast<B>(pA));

Is there any reason why this usage pattern is discouraged, and thus, equivalent functions to the ones present in shared_ptr are not provided for unique_ptr?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
betabandido
  • 18,946
  • 11
  • 62
  • 76
  • 4
    If the dynamic cast fails do you want the previously owned object to be destroyed? – CB Bailey Jun 12 '12 at 18:40
  • 2
    If the object pointed to by `pA` is not convertible to type `B` (that is, `dynamic_cast(pA.get())` fails), what do you want to happen to the object? Should `pA` retain ownership? Should it be destroyed? – cdhowie Jun 12 '12 at 18:41
  • 2
    @CharlesBailey That is actually a good point. That is actually an important implementation decision. Probably if `dynamic_cast` fails, "common sense" would advise to abort the casting, without modifying the original pointer. This is actually the behavior in cdhowie's answer. – betabandido Jun 12 '12 at 19:23

9 Answers9

41

In addition to Mark Ransom's answer, a unique_ptr<X, D> might not even store an X*.

If the deleter defines the type D::pointer then that's what is stored, and that might not be a real pointer, it only needs to meet the NullablePointer requirements and (if unique_ptr<X,D>::get() is called) have an operator* that returns X&, but it isn't required to support casting to other types.

unique_ptr is quite flexible and doesn't necessarily behave very much like a built-in pointer type.

As requested, here is an example where the stored type is not a pointer, and therefore casting is not possible. It's a bit contrived, but wraps a made-up database API (defined as a C-style API) in a C++ RAII-style API. The OpaqueDbHandle type meets the NullablePointer requirements, but only stores an integer, which is used as a key to lookup the actual DB connection via some implementation-defined mapping. I'm not showing this as an example of great design, just as an example of using unique_ptr to manage a non-copyable, movable resource which is not a dynamically-allocated pointer, where the "deleter" doesn't just call a destructor and deallocate memory when the unique_ptr goes out of scope.

#include <memory>

// native database API
extern "C"
{
  struct Db;
  int db_query(Db*, const char*);
  Db* db_connect();
  void db_disconnect(Db*);
}

// wrapper API
class OpaqueDbHandle
{
public:
  explicit OpaqueDbHandle(int id) : id(id) { }

  OpaqueDbHandle(std::nullptr_t) { }
  OpaqueDbHandle() = default;
  OpaqueDbHandle(const OpaqueDbHandle&) = default;

  OpaqueDbHandle& operator=(const OpaqueDbHandle&) = default;
  OpaqueDbHandle& operator=(std::nullptr_t) { id = -1; return *this; }

  Db& operator*() const;

  explicit operator bool() const { return id > 0; }

  friend bool operator==(const OpaqueDbHandle& l, const OpaqueDbHandle& r)
  { return l.id == r.id; }

private:
  friend class DbDeleter;
  int id = -1;
};

inline bool operator!=(const OpaqueDbHandle& l, const OpaqueDbHandle& r)
{ return !(l == r); }

struct DbDeleter
{
  typedef OpaqueDbHandle pointer;

  void operator()(pointer p) const;
};

typedef std::unique_ptr<Db, DbDeleter> safe_db_handle;

safe_db_handle safe_connect();

int main()
{
  auto db_handle = safe_connect();
  (void) db_query(&*db_handle, "SHOW TABLES");
}


// defined in some shared library

namespace {
  std::map<int, Db*> connections;      // all active DB connections
  std::list<int> unused_connections;   // currently unused ones
  int next_id = 0;
  const unsigned cache_unused_threshold = 10;
}

Db& OpaqueDbHandle::operator*() const
{
   return connections[id];
}

safe_db_handle safe_connect()
{
  int id;
  if (!unused_connections.empty())
  {
    id = unused_connections.back();
    unused_connections.pop_back();
  }
  else
  {
    id = next_id++;
    connections[id] = db_connect();
  }
  return safe_db_handle( OpaqueDbHandle(id) );
}

void DbDeleter::operator()(DbDeleter::pointer p) const
{
  if (unused_connections.size() >= cache_unused_threshold)
  {
    db_disconnect(&*p);
    connections.erase(p.id);
  }
  else
    unused_connections.push_back(p.id);
}
Community
  • 1
  • 1
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Thank you for your answer. It really looks as if it is more complicated than what I initially thought. Could you give an example of how to build a `unique_ptr` that does not store a pointer, please? – betabandido Jun 12 '12 at 22:46
  • 4
    +1 I never thought about using `unique_ptr` to implement a RAII approach for a database :) Thank you for the example. – betabandido Jun 14 '12 at 15:59
34

The functions you refer to each make a copy of the pointer. Since you can't make a copy of a unique_ptr it doesn't make sense to provide those functions for it.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 18
    That is true, but what if the only intention is to move the pointer? In that case, the casting functions for `unique_ptr` would not make a copy, just move (or transform) the pointer. – betabandido Jun 12 '12 at 19:29
  • 1
    @betabandido: And what if the dynamic cast fails? – Puppy Jun 13 '12 at 12:35
  • I finally decided to accept this answer, since even if it is possible, it seems there are a significant number of issues when trying to downcast a `unique_ptr`. – betabandido Jun 14 '12 at 16:03
  • @betabandido there are only issues, when you `dynamic_cast`... `static_cast`ing should be fine, but there is no function – IceFire Oct 10 '19 at 08:11
14

To build on Dave's answer, this template function will attempt to move the contents of one unique_ptr to another of a different type.

  • If it returns true, then either:
    • The source pointer was empty. The destination pointer will be cleared to comply with the semantic request of "move the contents of this pointer (nothing) into that one."
    • The object pointed to by the source pointer was convertible to the destination pointer type. The source pointer will be empty, and the destination pointer will point to the same object it used to point to. The destination pointer will receive the source pointer's deleter (only when using the first overload).
  • If it returns false, the operation was unsuccessful. Neither pointer will have changed state.

 

template <typename T_SRC, typename T_DEST, typename T_DELETER>
bool dynamic_pointer_move(std::unique_ptr<T_DEST, T_DELETER> & dest,
                          std::unique_ptr<T_SRC, T_DELETER> & src) {
    if (!src) {
        dest.reset();
        return true;
    }

    T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
    if (!dest_ptr)
        return false;

    std::unique_ptr<T_DEST, T_DELETER> dest_temp(
        dest_ptr,
        std::move(src.get_deleter()));

    src.release();
    dest.swap(dest_temp);
    return true;
}

template <typename T_SRC, typename T_DEST>
bool dynamic_pointer_move(std::unique_ptr<T_DEST> & dest,
                          std::unique_ptr<T_SRC> & src) {
    if (!src) {
        dest.reset();
        return true;
    }

    T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
    if (!dest_ptr)
        return false;

    src.release();
    dest.reset(dest_ptr);
    return true;
}

Note that the second overload is required for pointers declared std::unique_ptr<A> and std::unique_ptr<B>. The first function will not work because the first pointer will actually be of type std::unique_ptr<A, default_delete<A> > and the second of std::unique_ptr<A, default_delete<B> >; the deleter types won't be compatible and so the compiler will not allow you to use this function.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • So, given there is a possible implementation, can you think of any reason why dynamically casting a `unique_ptr` would be bad praxis? I agree that using `move` on `unique_ptr` may not be an example of good coding, but in some circumstances it may actually prove useful to do so. – betabandido Jun 12 '12 at 19:27
  • I can't say that I find it to be any worse than dynamically casting any other pointer. There's just the special "what happens if the cast fails" problem that you have to address (as I do in these functions) when you are dealing with exclusive-ownership pointers. – cdhowie Jun 12 '12 at 20:04
  • May want to check out some similar code http://stackoverflow.com/a/26377517/2746401 – user2746401 Sep 07 '15 at 09:33
  • @user2746401 Thanks for the link. I edited my answer to use `std::move()` when transferring the deleter as that answer does. – cdhowie Sep 14 '15 at 20:49
7

This isn't an answer to why, but it is a way to do it...

std::unique_ptr<A> x(new B);
std::unique_ptr<B> y(dynamic_cast<B*>(x.get()));
if(y)
    x.release();

It's not entirely clean since for a brief moment 2 unique_ptrs think they own the same object. And as was commented, you'll also have to manage moving a custom deleter if you use one (but that's very rare).

David
  • 27,652
  • 18
  • 89
  • 138
4

How about this for a C++11 approach:

template <class T_SRC, class T_DEST>
inline std::unique_ptr<T_DEST> unique_cast(std::unique_ptr<T_SRC> &&src)
{
    if (!src) return std::unique_ptr<T_DEST>();

    // Throws a std::bad_cast() if this doesn't work out
    T_DEST *dest_ptr = &dynamic_cast<T_DEST &>(*src.get());

    src.release();
    return std::unique_ptr<T_DEST>(dest_ptr);
}
Fedor
  • 17,146
  • 13
  • 40
  • 131
Bob F
  • 69
  • 1
  • 1
    I like that it throws on cast failure (for some use cases). But I think it's bad that it still returns a NULL/empty pointer when the source is empty. It creates an API inconsistency where you still return NULL in some cases where casting is impossible, but not in others. So maybe: if (!src) throw std::bad_cast()? – Giel Oct 21 '16 at 09:48
  • Thanks, really nice snippet. I've posted a refined version. – jaba Jan 27 '21 at 17:36
4

If you're only going the be using the downcast pointer in a small scope, one alternative is to simply downcast the reference to the object being managed by the unique_ptr:

auto derived = dynamic_cast<Derived&>(*pBase);
derived.foo();
Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
1

I refined the answer of @Bob F https://stackoverflow.com/a/14777419/1702991 so you only need one template parameter now as usual for other types of cast

template <class destinationT, typename sourceT>
std::unique_ptr<destinationT> unique_cast(std::unique_ptr<sourceT>&& source)
{
    if (!source)
        return std::unique_ptr<destinationT>();

    // Throws a std::bad_cast() if this doesn't work out
    destinationT* dest_ptr = &dynamic_cast<destinationT&>(*source.get());

    source.release();
    return std::unique_ptr<destinationT>(dest_ptr);
}

Update (non throwing version):

template <class destinationT, typename sourceT>
std::unique_ptr<destinationT> unique_cast(std::unique_ptr<sourceT>&& source)
{
    if (!source)
        return std::unique_ptr<destinationT>();

    destinationT* dest_ptr = dynamic_cast<destinationT*>(source.get());
    if(dest_ptr)
        source.release();
    
    return std::unique_ptr<destinationT>(dest_ptr);
}

Usage:

std::unique_ptr<MyClass> obj = unique_cast<MyClass>(std::make_unique<MyOtherClass>()); 
jaba
  • 735
  • 7
  • 18
  • This leaks memory if the `dynamic_cast` fails. You must only call `release` after checking that the cast was successful. See https://stackoverflow.com/a/26377517/1012586 – Morty May 04 '21 at 09:33
  • 1
    Thanks for pointing that out. But because there is a dynamic_cast to a reference the will be a std::bad_cast exception and release() will never be reached. The calling code must take care of the exception though. I've added a non throwing version though. – jaba May 05 '21 at 12:57
  • My bad! I missed the two ampersands in the cast. – Morty May 06 '21 at 09:47
0

I liked cdhowie's answer... but I wanted them to return instead of using out-args. Here's what I came up with:

template <typename T_DEST, typename T_SRC, typename T_DELETER>
std::unique_ptr<T_DEST, T_DELETER>
dynamic_pointer_cast(std::unique_ptr<T_SRC, T_DELETER> & src)
{
  if (!src)
    return std::unique_ptr<T_DEST, T_DELETER>(nullptr);

  T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
  if (!dest_ptr)
    return std::unique_ptr<T_DEST, T_DELETER>(nullptr);

  std::unique_ptr<T_DEST, T_DELETER> dest_temp(dest_ptr, std::move(src.get_deleter()));

  src.release();

  return dest_temp;
}

template <typename T_SRC, typename T_DEST>
std::unique_ptr<T_DEST>
dynamic_pointer_cast(std::unique_ptr<T_SRC> & src)
{
  if (!src)
    return std::unique_ptr<T_DEST>(nullptr);

  T_DEST * dest_ptr = dynamic_cast<T_DEST *>(src.get());
  if (!dest_ptr)
    return std::unique_ptr<T_DEST>(nullptr);

  std::unique_ptr<T_DEST> dest_temp(dest_ptr);

  src.release();

  return dest_temp;
}

I put it into a GitHub repo here: https://github.com/friedmud/unique_ptr_cast

friedmud
  • 670
  • 6
  • 7
0

With all the samples I've seen so far I have come up with this version.

template <typename T_DEST, typename T_SRC, typename T_DELETER>
std::unique_ptr<T_DEST, T_DELETER>
dynamic_pointer_cast(std::unique_ptr<T_SRC, T_DELETER>&& src)
{
    // When nullptr, just return nullptr
    if (!src) return std::unique_ptr<T_DEST, T_DELETER>(nullptr);

    // Perform dynamic_cast, throws std::bad_cast() if this doesn't work out
    T_DEST* dest_ptr = dynamic_cast<T_DEST*>(src.get());
    
    // Do not return nullptr on bad_cast
    //if (!dest_ptr) return std::unique_ptr<T_DEST, T_DELETER>(nullptr);
    
    // But throw std::bad_cast instead
    if (!dest_ptr) throw std::bad_cast();

    // Move into new unique_ptr
    std::unique_ptr<T_DEST, T_DELETER> dest_temp(dest_ptr, std::move(src.get_deleter()));
    src.release();

    return dest_temp;
}

template <typename T_DEST, typename T_SRC>
std::unique_ptr<T_DEST>
dynamic_pointer_cast(std::unique_ptr<T_SRC>&& src)
{
    // When nullptr, just return nullptr
    if (!src) return std::unique_ptr<T_DEST>(nullptr);

    // Perform dynamic_cast, throws std::bad_cast() if this doesn't work out
    T_DEST* dest_ptr = dynamic_cast<T_DEST*>(src.get());
    
    // Do not return nullptr on bad_cast
    //if (!dest_ptr) return std::unique_ptr<T_DEST>(nullptr);
    
    // But throw std::bad_cast instead
    if (!dest_ptr) throw std::bad_cast();

    // Move into new unique_ptr
    std::unique_ptr<T_DEST> dest_temp(dest_ptr);
    src.release();

    return dest_temp;
}

If you want to let dynamic_cast throw the std::bad_cast instead, just dynamic_cast to a reference instead

Use it like this

auto src = std::make_unique<Base>();
auto dst = dynamic_pointer_cast<Derived>(std::move(src));
auto dst2 = dynamic_pointer_cast<Derived>(FunctionReturningBase());
franckspike
  • 2,039
  • 25
  • 18