Dinámica de fundición para unique_ptr

Como es el caso de refuerzo, C++11 proporciona algunas funciones para la fundición shared_ptr:

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

Me pregunto, sin embargo, ¿por qué no hay equivalentes de funciones 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 que este patrón de uso está desaconsejado, y por lo tanto, funciones equivalentes a las presentes en shared_ptr no son proporcionados para unique_ptr?

  • Si la dinámica de la conversión falla ¿desea que el anteriormente propiedad de objeto a ser destruido?
  • Si el objeto apuntado por pA no convertibles a tipo de B (que es, dynamic_cast<B>(pA.get()) falla), ¿qué quieres que suceda al objeto? Debe pA conservar la propiedad? En caso de ser destruido?
  • Que en realidad es un buen punto. Que en realidad es una importante aplicación de la decisión. Probablemente si dynamic_cast falla, el «sentido común» aconsejaría a abortar en el casting, sin modificar el original puntero. Este es en realidad el comportamiento en cdhowie la respuesta.
InformationsquelleAutor betabandido | 2012-06-12

7 Kommentare

  1. 32

    Las funciones que se refieren a cada una copia del puntero. Puesto que usted no puede hacer una copia de un unique_ptr no tiene sentido para proporcionar estas funciones para ello.

    • Eso es cierto, pero ¿y si la única intención es mover el puntero? En ese caso, el reparto de funciones para unique_ptr no puede hacer una copia, sólo se mueven (o transformación) el puntero.
    • ¿Y si la dinámica de la conversión falla?
    • Finalmente me decidí a aceptar esta respuesta, ya que incluso si es posible, parece que hay un número significativo de problemas al intentar abatido un unique_ptr.
    • sólo hay problemas, cuando dynamic_caststatic_casting debería de estar bien, pero no hay ninguna función
  2. 38

    Además de la Marca de Rescate del respuesta, un unique_ptr<X, D> ni siquiera puede almacenar un X*.

    Si el deleter define el tipo D::pointer entonces que es lo que se almacena, y que podría no ser un verdadero puntero, no sólo debe cumplir con la NullablePointer y requisitos (si unique_ptr<X,D>::get() se llama) tiene un operator* que devuelve X&, pero no es necesario para apoyar la conversión a otros tipos.

    unique_ptr es bastante flexible y no necesariamente se comportan de forma muy similar a un built-in tipo de puntero.

    Conforme a lo solicitado, este es un ejemplo donde la almacenan tipo no es un puntero, y por lo tanto, la conversión no es posible. Es un poco artificial, pero envuelve un API de base de datos (que se define como un estilo C API) en C++ RAII-estilo de la API. El OpaqueDbHandle tipo cumple la NullablePointer requisitos, pero sólo almacena un entero, que se utiliza como una clave para la búsqueda de la real DB conexión a través de alguna aplicación definidos de asignación. No estoy mostrando esto como un ejemplo de gran diseño, así como un ejemplo del uso de unique_ptr para gestionar un no copiable, muebles de recursos que no es un asignados de forma dinámica puntero, donde el «deleter» no sólo llamar a un destructor y desasignar la memoria cuando el unique_ptr sale del ámbito.

    #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);
    }
    • Gracias por tu respuesta. Realmente parece como si el asunto es más complicado de lo que pensaba al principio. ¿Podría dar un ejemplo de cómo construir una unique_ptr que no almacena un puntero, por favor?
    • +1 yo nunca pensé acerca del uso de unique_ptr para implementar un RAII enfoque para una base de datos 🙂 Gracias por el ejemplo.
  3. 12

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

    • Si devuelve true, entonces:
      • La fuente puntero estaba vacía. El destino puntero se borrará para cumplir con la semántica en la solicitud de «mover el contenido de este puntero (nada) en el que uno».
      • El objeto señalado por la fuente puntero era convertible para el destino tipo de puntero. La fuente puntero estará vacía, y el destino puntero se apuntan al mismo objeto se utiliza para señalar. El destino puntero recibirá el origen del puntero deleter (sólo cuando se utiliza la primera de sobrecarga).
    • Si devuelve false, la operación no tuvo éxito. Ni el puntero se han 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 punteros declarado std::unique_ptr<A> y std::unique_ptr<B>. La primera función no funcionará porque el primer puntero realmente de tipo std::unique_ptr<A, default_delete<A> > y el segundo de std::unique_ptr<A, default_delete<B> >; el deleter tipos no es compatible y por lo que el compilador no permitirá el uso de esta función.

    • Por tanto, y dado que hay una posible implementación, puede usted pensar en alguna razón por la dinámica de fundición de una unique_ptr sería una mala praxis? Estoy de acuerdo en que el uso de move en unique_ptr no puede ser un ejemplo de buena codificación, pero en algunas circunstancias puede realmente ser útil para hacerlo.
    • No puedo decir que me parece que es peor que la dinámica de fundición de cualquier otro puntero. Solo hay un especial de «¿qué pasa si la conversión falla» problema que tiene la dirección (como hago yo en estas funciones) cuando se trata de la exclusiva propiedad de los punteros.
    • Puede que desee comprobar hacia fuera algunos de código similares stackoverflow.com/a/26377517/2746401
    • Gracias por el enlace. He editado mi respuesta a usar std::move() cuando la transferencia de la deleter como respuesta.
  4. 5

    Esta no es una respuesta a por qué, pero es una manera de hacerlo…

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

    No es del todo limpio ya que por un breve momento 2 unique_ptrs piensan que son los dueños del mismo objeto. Y como se comentó, además, tendrás que gestionar mover una costumbre deleter si el uso de una (pero que muy raro).

    • Es más complicado si tienes un unique_ptr<A, Deleter> como usted necesita para mover el deleter.
    • A pesar de que no hay ningún problema con tener dos instancias temporalmente: std::unique_ptr<a> x(nuevo B); const auto yp = dynamic_cast<B*>(x.get()); std::unique_ptr<B> y(yp != nullptr ? (x.release(), yp) : nullptr);
  5. 3

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

    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);
    }
    • Me gusta que se lanza en fundición de fracaso (para algunos casos de uso). Pero creo que es malo que todavía se devuelve un valor NULL/puntero vacío cuando la fuente está vacía. Crea una API inconsistencia donde todavía puede devolver un valor NULO en algunos casos en los que el casting es imposible, pero no en otros. Así que tal vez: si (!src) tiro std::bad_cast()?
  6. 2

    Si sólo va a ser el uso de los humildes puntero en un pequeño ámbito, una alternativa es simplemente abatido el referencia al objeto que está siendo gestionado por la unique_ptr:

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

    Me gustó cdhowie la respuesta… pero yo quería que ellos volver en lugar de mediante argumentos. He aquí lo que encontré:

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

    Yo las puse en un repo en GitHub aquí: https://github.com/friedmud/unique_ptr_cast

Kommentieren Sie den Artikel

Bitte geben Sie Ihren Kommentar ein!
Bitte geben Sie hier Ihren Namen ein

Pruebas en línea