unique_ptr a una clase derivada como argumento a una función que lleva un unique_ptr a una clase base

4 minutos de lectura

avatar de usuario
svick

Estoy tratando de usar un unique_ptr a la clase derivada en una función que toma un unique_ptr a una clase base. Algo como:

class Base {};

class Derived : public Base {};

void f(unique_ptr<Base> const &base) {}

…

unique_ptr<Derived> derived = unique_ptr<Derived>(new Derived);
f(derived);

Si entiendo esta respuesta correctamente, este código debería funcionar, pero provoca los siguientes errores de compilación:

error C2664: ‘f’: no ​​se puede convertir el parámetro 1 de ‘std::unique_ptr<_ty>‘ a ‘const std::unique_ptr<_ty> &’

IntelliSense: no existe una conversión adecuada definida por el usuario de “std::unique_ptr>” a “const std::unique_ptr>”

si cambio f tomar unique_ptr<Derived> const &derivedfunciona bien, pero eso no es lo que quiero.

¿Estoy haciendo algo mal? ¿Qué puedo hacer para evitar esto?

Estoy usando Visual Studio 2012.

avatar de usuario
Kerrek SB

Tienes tres opciones:

  1. Renunciar a la propiedad. Esto dejará su variable local sin acceso al objeto dinámico después de la llamada a la función; el objeto ha sido transferido al destinatario:

    f(std::move(derived));
    
  2. Cambiar la firma de f:

    void f(std::unique_ptr<Derived> const &);
    
  3. Cambia el tipo de tu variable:

    std::unique_ptr<base> derived = std::unique_ptr<Derived>(new Derived);
    

    O por supuesto simplemente:

    std::unique_ptr<base> derived(new Derived);
    

    O incluso:

    std::unique_ptr<base> derived = std::make_unique<Derived>();
    
  4. Actualizar: O, como se recomienda en los comentarios, no transfiera la propiedad en absoluto:

    void f(Base & b);
    
    f(*derived);
    

  • En ese caso, estoy considerando ir con 4. Use shared_ptr.

    – svick

    4 de julio de 2013 a las 15:37

  • ¿Qué tal si usamos una referencia en lugar de un unique_ptr para la llamada a la función?

    – ltjax

    4 de julio de 2013 a las 15:51

  • @svick, ¿por qué pasar un puntero inteligente a la función si no se apropia del puntero? Para eso no están los punteros inteligentes.

    –Jonathan Wakely

    4 de julio de 2013 a las 15:56

  • @metal: Genial, gracias, sí, detalle importante, los tipos de devolución deben coincidir. Me lo perdí. Buen material. podrías haber dicho return std::unique_ptr<Base>(std::move(p))Supongo.

    – KerrekSB

    25 de febrero de 2014 a las 15:26


  • “Esto destruirá el objeto al final de la llamada a la función”: Pero no sabemos qué f() está haciendo (suponiendo que en realidad no está vacío), podría pasar la propiedad a otra parte. ¿No es todo lo que podemos decir que renunciamos o transferimos la propiedad?

    – Zitrax

    10 de enero de 2017 a las 10:11


Tenía la opción n. ° 1 de la respuesta aceptada y todavía tenía el mismo error de compilación. Me golpeé la cabeza contra la pared durante más de una hora y finalmente me di cuenta de que había

class Derived : Base {};

en vez de

class Derived : public Base {};

avatar de usuario
hmjd

Una posible solución es cambiar el tipo de argumento para que sea un Base const*y pasar derived.get() en cambio. No hay transferencia de propiedad con unique_ptr const<Base>& (y el unique_ptr no se está modificando), por lo que cambiar a un Base const* no cambia el significado.


Herb Sutter analiza detalladamente el paso de argumentos de puntero inteligente en Parámetros de puntero inteligente. Un extracto del artículo vinculado se refiere a esta situación exacta:

pasando un const unique_ptr<widget>& es extraño porque solo puede aceptar cualquiera null o un widget cuyo tiempo de vida pasa a ser administrado en el código de llamada a través de un unique_ptr, y el destinatario de la llamada generalmente no debería preocuparse por la elección de administración de por vida de la persona que llama. Paso widget* cubre un superconjunto estricto de estos casos y puede aceptar “null o un widget” independientemente de la política de por vida que esté usando la persona que llama.

  • No Base const* significa que la función podría almacenar el puntero en algún lugar? Pensé que los punteros inteligentes están tratando de evitar eso.

    – svick

    4 de julio de 2013 a las 16:04


  • @svick, la función podría almacenar base.get() con la misma facilidad (get() es const función miembro).

    – hmjd

    4 de julio de 2013 a las 16:05


  • @svick No. Los punteros inteligentes son para evitar pérdidas de memoria al dejar en claro quién es el propietario de un objeto. Si pasa un puntero sin procesar como argumento a una función, solo le da acceso para leerlo/modificarlo. Los punteros inteligentes son para evitar el uso de raw new y deleteno punteros en absoluto.

    – etam1024

    5 de julio de 2013 a las 0:08

Otra forma es cambiar la firma de f y usarlo de una manera ligeramente diferente:

void f(Base* base_ptr) {
    // take ownership inside the function
    std::unique_ptr<Base> base {base_ptr};
    // ...
}

// ...
auto derived = std::make_unique<Derived>();
f(derived.release());  // release ownership

¿Ha sido útil esta solución?