Pasando shared_ptr <Derived> como shared_ptr <Base>

93

¿Cuál es el mejor método para pasar a shared_ptrde un tipo derivado a una función que toma a shared_ptrde un tipo base?

Generalmente paso shared_ptrs por referencia para evitar una copia innecesaria:

int foo(const shared_ptr<bar>& ptr);

pero esto no funciona si trato de hacer algo como

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

Podría usar

foo(dynamic_pointer_cast<Base, Derived>(bar));

pero esto parece subóptimo por dos razones:

  • A dynamic_castparece un poco excesivo para un elenco simple derivado a base.
  • Según tengo entendido, dynamic_pointer_castcrea una copia (aunque temporal) del puntero para pasar a la función.

¿Existe una solución mejor?

Actualización para la posteridad:

Resultó ser un problema de falta de archivo de encabezado. Además, lo que estaba tratando de hacer aquí se considera un antipatrón. Generalmente,

  • Las funciones que no afectan la vida útil de un objeto (es decir, el objeto sigue siendo válido durante la duración de la función) deben tomar una referencia simple o un puntero, por ejemplo int foo(bar& b).

  • Las funciones que consumen un objeto (es decir, son los usuarios finales de un objeto dado) deben tomar un unique_ptrvalor por ej int foo(unique_ptr<bar> b). Las personas que llaman deben incluir std::moveel valor en la función.

  • Las funciones que extienden la vida útil de un objeto deben tomar un shared_ptrvalor por ejemplo, por ejemplo int foo(shared_ptr<bar> b). Se aplica el consejo habitual para evitar referencias circulares .

Consulte la charla Back to Basics de Herb Sutter para obtener más detalles.

Matt Kline
fuente
8
¿Por qué quieres pasar un shared_ptr? ¿Por qué no hay referencia constante de barra?
ipc
2
Cualquier dynamicyeso solo es necesario para abatir. Además, pasar el puntero derivado debería funcionar bien. Creará un nuevo shared_ptrcon el mismo refcount (y lo aumentará) y un puntero a la base, que luego se une a la referencia constante. Sin embargo, dado que ya está tomando una referencia, no veo por qué quiere tomar una shared_ptr. Toma un Base const&y llama foo(*bar).
Xeo
@Xeo: Pasar el puntero derivado (es decir foo(bar)) no funciona, al menos en MSVC 2010.
Matt Kline
1
¿Qué quieres decir con "obviamente no funciona"? El código se compila y se comporta correctamente; ¿se pregunta cómo evitar crear un temporal shared_ptrpara pasar a la función? Estoy bastante seguro de que no hay forma de evitarlo.
Mike Seymour
1
@Seth: No estoy de acuerdo. Creo que hay motivos para pasar un puntero compartido por valor, y hay muy pocos motivos para pasar un puntero compartido por referencia (y todo esto sin recomendar copias innecesarias). Razonamiento aquí stackoverflow.com/questions/10826541/…
R. Martinho Fernandes

Respuestas:

47

Aunque Basey Derivedson indicadores covariantes y crudos, actuarán en consecuencia shared_ptr<Base>y noshared_ptr<Derived> son covariantes. El es la manera correcta y sencilla de manejar este problema.dynamic_pointer_cast

( Editar: static_pointer_cast sería más apropiado porque está transmitiendo de derivado a base, que es seguro y no requiere verificaciones de tiempo de ejecución. Consulte los comentarios a continuación).

Sin embargo, si su foo()función no desea participar en la extensión de la vida útil (o, más bien, participar en la propiedad compartida del objeto), entonces es mejor aceptar const Base&y eliminar la referencia shared_ptral pasarlo a foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Además, dado que los shared_ptrtipos no pueden ser covariantes, las reglas de conversiones implícitas entre tipos de devolución covariantes no se aplican al devolver tipos de shared_ptr<T>.

Bret Kuhns
fuente
39
No son covariantes, pero shared_ptr<Derived>se pueden convertir implícitamente en shared_ptr<Base>, por lo que el código debería funcionar sin travesuras de casting.
Mike Seymour
9
Um, shared_ptr<Ty>tiene un constructor que toma a shared_ptr<Other>y hace la conversión apropiada si Ty*es implícitamente convertible a Other*. Y si se necesita un yeso, static_pointer_castes el apropiado aquí, no dynamic_pointer_cast.
Pete Becker
Es cierto, pero no con su parámetro de referencia, como en la pregunta. Sin embargo, necesitaría hacer una copia. Pero, si está usando refs to shared_ptr's para evitar el recuento de referencias, entonces realmente no hay una buena razón para usar a shared_ptren primer lugar. Es mejor usar const Base&en su lugar.
Bret Kuhns
@PeteBecker Vea mi comentario a Mike sobre el constructor de conversión. Sinceramente, no sabía nada static_pointer_cast, gracias.
Bret Kuhns
1
@TanveerBadar No estoy seguro. ¿Quizás esto no pudo compilarse en 2012? (específicamente usando Visual Studio 2010 o 2012). Pero tiene toda la razón, el código de OP debería compilarse absolutamente si la definición completa de una clase / derivada públicamente / es visible para el compilador.
Bret Kuhns
32

Esto también sucederá si ha olvidado especificar la herencia pública en la clase derivada, es decir, si como yo, escribe esto:

class Derived : Base
{
};
pastor
fuente
classes para parámetros de plantilla; structes para definir clases. (Esto es a lo sumo un 45% una broma.)
Davis Herring
Esto definitivamente debe considerarse la solución, no hay necesidad de un elenco ya que solo falta público.
Alexis Paques
12

Parece que te estás esforzando demasiado. shared_ptres barato de copiar; ese es uno de sus objetivos. Pasarlos por referencia no logra mucho. Si no desea compartir, pase el puntero sin procesar.

Dicho esto, hay dos formas de hacer esto en las que puedo pensar:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));
Pete Becker
fuente
9
No, no son baratos de copiar, deben pasarse por referencia siempre que sea posible.
Seth Carnegie
6
@SethCarnegie: ¿Herb hizo un perfil de su código para ver si pasar por valor era un cuello de botella?
Pete Becker
25
@SethCarnegie: eso no responde a la pregunta que hice. Y, por si sirve de algo, escribí la shared_ptrimplementación que envía Microsoft.
Pete Becker
6
@SethCarnegie: tienes la heurística al revés. Las optimizaciones manuales generalmente no deben realizarse a menos que pueda demostrar que son necesarias.
Pete Becker
21
Es sólo una optimización "prematura" si necesita trabajar en ella. No veo ningún problema en adoptar modismos eficientes en lugar de ineficientes, ya sea que marque la diferencia en un contexto particular o no.
Mark Ransom
11

También verifique que el #includedel archivo de encabezado que contiene la declaración completa de la clase derivada esté en su archivo fuente.

Tuve este problema. El std::shared<derived>no lanzaría a std::shared<base>. Había declarado hacia adelante ambas clases para poder contener punteros hacia ellas, pero como no tenía el, #includeel compilador no podía ver que una clase se derivaba de la otra.

Phil Rosenberg
fuente
1
Vaya, no me lo esperaba, pero esto me solucionó. Estaba siendo muy cuidadoso y solo incluía archivos de encabezado donde los necesitaba, por lo que algunos de ellos solo estaban en archivos de origen y los declaraba en encabezados como usted dijo.
jigglypuff
El compilador estúpido es estúpido. Este era mi problema. ¡Gracias!
Tanveer Badar