Dynamic casting for unique_ptr

As with 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 equivalent 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 a reason why this usage pattern is not recommended, and thus for unique_ptr ?

equivalent functions are not provided to those that are present in shared_ptr
+46
c ++ casting c ++ 11 smart-pointers unique-ptr
Jun 12 '12 at 18:31
source share
6 answers

The functions you access make a copy of the pointer. Since you cannot make a copy of unique_ptr , it makes no sense to provide these functions for it.

+29
Jun 12 '12 at 18:40
source share

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

If the deleter determines the type D::pointer , then what is stored and which may not be a real pointer, it must satisfy the requirements of NullablePointer and (if unique_ptr<X,D>::get() is called) has an operator* that returns X& but listing support for other types is not required.

unique_ptr quite flexible and does not 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. This is a bit far-fetched, but completes the created database API (defined as the C API) in the C ++ RAII API. The OpaqueDbHandle type satisfies the requirements of NullablePointer , but only stores an integer that is used as a key to search for the actual database connection through a specific mapping defined by the implementation. I don’t show it as an example of great design, as an example of using unique_ptr to manage an unique_ptr -copied movable resource, which is not a dynamically allocated pointer, where β€œdeleter” does not just call the destructor and free memory when unique_ptr goes beyond.

 #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); } 
+38
Jun 12 '12 at 18:51
source share

To build an answer to Dave, this template function will try to move the contents of one unique_ptr to another type.

  • If it returns true, then either:
    • The source pointer was empty. The destination pointer will be cleared to execute the semantic query "move the contents of this pointer (nothing) to this."
    • The object pointed to by the source pointer has been converted to the type of the destination pointer. The source pointer will be empty, and the destination pointer will point to the same object that it was pointing to. The destination pointer will receive the source pointer debugger (only when using the first overload).
  • If it returns false, the operation failed. No pointer will change 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 a second overload is required for pointers declared by 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> > ; Debit types will not be compatible, so the compiler will not allow you to use this function.

+11
Jun 12 '12 at 19:01
source share

This is not an answer to the question 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(); 

This is not entirely clean, since for a short time 2 unique_ptr believe that they have the same object. And, as was commented, you will also have to control the movement of the user deleter if you use one (but this is very rare).

+5
Jun 12 '12 at 18:41
source share

How about this for a C ++ 11 approach:

 template <class T_SRC, class T_DEST> 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> ret(dest_ptr); } 
+3
Feb 08 '13 at 16:49
source share

If you are only going to use the drop pointer in a small scope, one alternative is to simply drop the reference to the object managed with unique_ptr :

 auto derived = dynamic_cast<Derived&>(*pBase); derived.foo(); 
+2
Aug 15 '16 at 1:35
source share



All Articles