Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic casting for unique_ptr

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?

like image 836
betabandido Avatar asked Jun 12 '12 18:06

betabandido


People also ask

Why would you choose shared_ptr instead of unique_ptr?

In short: Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.

Can unique_ptr be assigned?

It can be assigned: class owner { std::unique_ptr<someObject> owned; public: owner() { owned=std::unique_ptr<someObject>(new someObject()); } };

Do I need to delete unique_ptr?

An explicit delete for a unique_ptr would be reset() . But do remember that unique_ptr are there so that you don't have to manage directly the memory they hold. That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.

Can unique_ptr be moved?

A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it. We recommend that you restrict an object to one owner, because multiple ownership adds complexity to the program logic.


1 Answers

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); } 
like image 52
Jonathan Wakely Avatar answered Sep 23 '22 21:09

Jonathan Wakely