unique_ptr smart c++ casting c++11 smart-pointers unique-ptr

c++ - smart - Fundición dinámica para unique_ptr



smart pointers c++ (6)

Como fue el caso en Boost, C ++ 11 proporciona algunas funciones para shared_ptr :

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

Me pregunto, sin embargo, por qué no hay funciones equivalentes para unique_ptr .

Considere el siguiente ejemplo sencillo:

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));

¿Hay alguna razón por la cual se desaconseja este patrón de uso, y por lo tanto, las funciones equivalentes a las presentes en shared_ptr no se proporcionan para unique_ptr ?


¿Qué tal esto para un enfoque C ++ 11:

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); }


Además de la answer Mark Ransom, un unique_ptr<X, D> podría incluso no almacenar una X* .

Si el eliminador define el tipo D::pointer entonces eso es lo que está almacenado, y puede que no sea un puntero real, solo necesita cumplir con los requisitos de NullablePointer y (si se unique_ptr<X,D>::get() ) tener un operator* que devuelve X& , pero no es necesario para admitir el lanzamiento a otros tipos.

unique_ptr es bastante flexible y no se comporta necesariamente como un tipo de puntero incorporado.

Tal como se solicitó, aquí hay un ejemplo en el que el tipo almacenado no es un puntero, y por lo tanto no es posible la conversión. Es un poco artificial, pero envuelve una API de base de datos inventada (definida como una API de estilo C) en una API de estilo C ++ RAII. El tipo OpaqueDbHandle cumple los requisitos de NullablePointer , pero solo almacena un entero, que se utiliza como clave para buscar la conexión de base de datos real a través de una asignación definida por la implementación. No estoy mostrando esto como un ejemplo de gran diseño, solo como un ejemplo de uso de unique_ptr para administrar un recurso móvil no copiable que no es un puntero asignado dinámicamente, donde el "eliminador" no solo llama a un destructor y desasignar la memoria cuando el unique_ptr queda fuera del alcance.

#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); }


Esta no es una respuesta a por qué , pero es una forma de hacerlo ...

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

No está completamente limpio ya que por un breve momento 2 unique_ptr s creen que poseen el mismo objeto. Y como se comentó, también tendrá que administrar mover un eliminador personalizado si usa uno (pero eso es muy raro).


Las funciones que refiere a cada una hacen una copia del puntero. Como no puede hacer una copia de un unique_ptr , no tiene sentido proporcionar esas funciones para él.


Para construir sobre la respuesta de Dave, esta función de plantilla intentará mover el contenido de un unique_ptr a otro de un tipo diferente.

  • Si devuelve verdadero, entonces:
    • El puntero fuente estaba vacío. El puntero de destino se borrará para cumplir con la solicitud semántica de "mover los contenidos de este puntero (nada) a ese".
    • El objeto apuntado por el puntero de origen fue convertible al tipo de puntero de destino. El puntero fuente estará vacío y el puntero de destino apuntará al mismo objeto al que apuntaba. El puntero de destino recibirá el eliminador del puntero de origen (solo cuando se usa la primera sobrecarga).
  • Si devuelve falso, la operación no tuvo éxito. Ningún puntero habrá cambiado de estado.

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; }

Tenga en cuenta que la segunda sobrecarga es necesaria para los punteros declarados std::unique_ptr<A> y std::unique_ptr<B> . La primera función no funcionará porque el primer puntero en realidad será de tipo std::unique_ptr<A, default_delete<A> > y el segundo de std::unique_ptr<A, default_delete<B> > ; los tipos de borrado no serán compatibles y el compilador no le permitirá usar esta función.


Si solo va a utilizar el puntero abatido en un alcance pequeño, una alternativa es simplemente bajar la referencia al objeto que está gestionando el unique_ptr :

auto derived = dynamic_cast<Derived&>(*pBase); derived.foo();