2008-09-22 9 views
22

Considere estas clases.Obteniendo un vector <Derived*> en una función que espera un vector <Base*>

class Base 
{ 
    ... 
}; 

class Derived : public Base 
{ 
    ... 
}; 

esta función

void BaseFoo(std::vector<Base*>vec) 
{ 
    ... 
} 

Y por último, mi vectorial

std::vector<Derived*>derived; 

quiero pasar derived para funcionar BaseFoo, pero el compilador no me deja. ¿Cómo resuelvo esto, sin copiar todo el vector en un std::vector<Base*>?

+0

Creo que necesita aclarar si BaseFoo está destinado a tomar el vector por const o sin referencia de referencia (sería muy raro que lo tome por valor). En otras palabras, ¿necesita BaseFoo modificar el vector? –

Respuesta

41

vector<Base*> y vector<Derived*> son tipos no relacionados, por lo que no puede hacer esto. Esto se explica en las preguntas más frecuentes de C++ here.

Puede cambiar su variable de vector<Derived*> a vector<Base*> e insertar Derived objetos en ella.

Además, debe pasar el vector mediante const-referencia, no por valor:

void BaseFoo(const std::vector<Base*>& vec) 
{ 
    ... 
} 

Por último, para evitar pérdidas de memoria, y hacer que su código de excepción de seguridad, considerar el uso de un recipiente diseñado para manejar montón objetos -allocated, por ejemplo:

#include <boost/ptr_container/ptr_vector.hpp> 
boost::ptr_vector<Base> vec; 

Alternativamente, cambian el vector para sostener un puntero inteligente en lugar de usar punteros primas:

#include <memory> 
std::vector< std::shared_ptr<Base*> > vec; 

o

#include <boost/shared_ptr.hpp> 
std::vector< boost::shared_ptr<Base*> > vec; 

En cada caso, lo que se necesita para modificar su función BaseFoo en consecuencia.

+3

+1: respuesta completa y precisa. – ereOn

8

una opción es utilizar una plantilla

template<typename T> 
void BaseFoo(const std::vector<T*>& vec) 
{ 
... 
} 

El inconveniente es que la aplicación tiene que estar en la cabecera y obtendrá un poco de hinchazón de código. Concluirá con diferentes funciones que se instanciarán para cada tipo, pero el código permanece igual. Dependiendo del caso de uso, es una solución rápida y sucia.

Editar, debo tener en cuenta que la razón por la que necesitamos una plantilla aquí es porque estamos tratando de escribir el mismo código para los tipos no relacionados, como lo señalan otros carteles. Las plantillas le permiten resolver estos problemas exactos. También lo actualicé para usar una referencia constante. También debe pasar objetos "pesados" como un vector por referencia constante cuando no necesite una copia, que es básicamente siempre.

+0

Potencialmente, el compilador puede optimizar las dos instancias de la función en una sola al darse cuenta de que el código objeto generado es exactamente el mismo. Sin embargo, muchos lo harían con una función complicada. –

+0

Interesante, he visto al enlazador descartar el código redundante. –

+0

Puede evitar la necesidad de definir la función en el archivo de encabezado mediante el uso de instancias explícitas. Puede combinar mi ejemplo (a continuación) en su respuesta. –

0

Son tipos no relacionados: no se puede.

2

En general, comenzaría con un contenedor de punteros base, no a la inversa.

+0

Creo que ambos casos ocurren en la práctica. Pero estoy de acuerdo en que un vector es más común en un buen diseño OO. –

1

Si trata de una biblioteca de terceros, y esta es su única esperanza, entonces usted puede hacer esto:

BaseFoo (*reinterpret_cast<std::vector<Base *> *>(&derived)); 

arreglar lo contrario su código con una de las otras sugerencia sobre.

+1

Si bien eso podría funcionar, se siente como una bomba de tiempo del código. Tengo muchas dudas sobre el uso de reinterpret_cast a menos que sepa exactamente lo que estoy haciendo. –

+0

Simplemente no hagas eso, no está garantizado que funcione. –

+1

Esta es la única respuesta a la pregunta. Las otras respuestas son básicamente "evitar ese problema": estos son buenos consejos, pero no siempre son aplicables. Podría ser un poco más simple: BaseFoo (reinterpret_cast &> (derivado)); – 0xF

22

En lugar de pasar el objeto contenedor (vector<>), pase los iteradores begin y end como el resto de los algoritmos STL. La función que los recibe se modelará y no importará si pasa Derived * o Base *.

+2

+1: lo más fácil de hacer sin impulso o puntero compartido (que aún no sé) – nkint

1

Si std::vector apoyaron lo que estás pidiendo, entonces sería posible derrotar el sistema de tipos C++ sin utilizar ningún moldes (edit: link de ChrisN a las conversaciones C++ FAQ Lite sobre el mismo tema):

class Base {}; 
class Derived1 : public Base {}; 
class Derived2 : public Base {}; 

void pushStuff(std::vector<Base*>& vec) { 
    vec.push_back(new Derived2); 
    vec.push_back(new Base); 
} 

... 
std::vector<Derived1*> vec; 
pushStuff(vec); // Not legal 
// Now vec contains a Derived2 and a Base! 

Dado que su función BaseFoo() toma el vector por valor, no puede modificar el vector original que pasó, por lo que lo que escribí no sería posible. Pero si toma una referencia no constante y usa reinterpret_cast<std::vector<Base*>&>() para pasar su std::vector<Derived*>, es posible que no obtenga el resultado que desea y su programa podría bloquearse.

Las matrices de Java admiten covariant subtyping, y esto requiere Java a do a runtime type check every time you store a value in an array. Esto también es indeseable.

2

Tomando Matt Price's respuesta desde arriba, ya que se sabe de antemano qué tipo desea usar con su función, se puede declarar la plantilla de función en el archivo de cabecera, y luego añadir instanciaciones explícitas para esos tipos:

// BaseFoo.h 
template<typename T> 
void BaseFoo(const std::vector<T*>& vec); 

// BaseFoo.cpp 
template<typename T> 
void BaseFoo(const std::vector<T*>& vec); 
{ 
... 
} 

// Explicit instantiation means no need for definition in the header file. 
template void BaseFoo<Base> (const std::vector<Base*>& vec); 
template void BaseFoo<Derived> (const std::vector<Derived*>& vec); 
12

Este problema ocurre en los lenguajes de programación que tienen contenedores mutables. No puede pasar una bolsa mutable de manzanas como una bolsa de fruta porque no puede estar seguro de que otra persona no ponga un limón en esa bolsa de fruta, después de lo cual ya no califica como una bolsa de manzanas. Si la bolsa de manzanas no fuera mutable, pasarla como una bolsa de fruta estaría bien. Buscar covarianza/contravariancia.

Cuestiones relacionadas