2010-02-04 23 views
28

Tengo esta estructura de clases.Polimorfismo de plantillas C++

class Interface{ 
... 
} 

class Foo : public Interface{ 
... 
} 

template <class T> 
class Container{ 
... 
} 

Y tengo este constructor de alguna otra clase de barra.

Bar(const Container<Interface> & bar){ 
... 
} 

Cuando llamo al constructor de esta manera obtengo el error "no matching function".

Container<Foo> container(); 

Bar * temp = new Bar(container); 

¿Qué pasa? ¿Las plantillas no son polimórficas?

+0

Las plantillas no son polimórficas. Las plantillas están vinculadas en tiempo de compilación, a diferencia de los objetos polimórficos que están vinculados en tiempo de ejecución. –

+0

Preguntas relacionadas: http://stackoverflow.com/questions/1289167/template-polymorphism-not-working http://stackoverflow.com/questions/639248/c-covariant-templates –

Respuesta

38

creo que la terminología exacta para lo que necesita es "covarianza de plantilla", lo que significa que si B hereda de A, entonces de alguna manera T<B> hereda de T<A>. Este no es el caso en C++, ni lo es con los genéricos Java y C# *.

Hay una buena razón para evitar la covarianza de la plantilla: esto simplemente eliminará todo tipo de seguridad en la clase de plantilla. Voy a explicar con el siguiente ejemplo:

//Assume the following class hierarchy 
class Fruit {...}; 

class Apple : public Fruit {...}; 

class Orange : public Fruit {...}; 

//Now I will use these types to instantiate a class template, namely std::vector 
int main() 
{ 
    std::vector<Apple> apple_vec; 
    apple_vec.push_back(Apple()); //no problem here 

    //If templates were covariant, the following would be legal 
    std::vector<Fruit> & fruit_vec = apple_vec; 

    //push_back would expect a Fruit, so I could pass it an Orange 
    fruit_vec.push_back(Orange()); 

    //Oh no! I just added an orange in my apple basket! 
} 

En consecuencia, se debe considerar T<A> y T<B> tipos que no guardan relación alguna, independientemente de la relación entre A y B.

Entonces, ¿cómo puede usted solucionar el problema que' mirando hacia atrás? En Java y C#, se puede usar respectivamente comodines acotadas y limitaciones:

//Java code 
Bar(Container<? extends Interface) {...} 

//C# code 
Bar<T>(Container<T> container) where T : Interface {...} 

El próximo C++ estándar (conocido como C++ 1x (antes C++ 0x)) inicialmente contenía una aún más potente mecanismo denominado Concepts, que habría permitido a los desarrolladores aplicar requisitos sintácticos y/o semánticos en los parámetros de la plantilla, pero desafortunadamente se pospuso a una fecha posterior. Sin embargo, Boost tiene un Concept Check library que puede interesarle.

Sin embargo, los conceptos pueden ser un poco excesivos para el problema que encuentre, y usar una simple afirmación estática como la propuesta por @gf es probablemente la mejor solución.

* Actualización: desde .NET Framework 4, es posible marcar los parámetros genéricos ha sido covariant or contravariant.

5

No. Imagine que el parámetro contenedor está "codificado" en la clase que define (y así es como funciona). Por lo tanto, el tipo de contenedor es Container_Foo, que no es compatible con Container_Interface.

Lo que se puede tratar sin embargo, es la siguiente:

template<class T> 
Bar(const Container<T> & bar){ 
... 
} 

Sin embargo, se suelta la comprobación de tipo directo de esa manera.

en realidad la forma STL (probablemente más eficaz y genérico) habría que hacer

template<class InputIterator> 
Bar(InputIterator begin, InputIterator end){ 
... 
} 

... pero supongo que no tiene iteradores implementadas en el contenedor.

+0

Eso es muy triste. Gracias por el consejo. No me gusta esa solución, pero me temo que es la única que queda. –

+0

Usted asume correcto. No los necesito de esa manera en particular. Francamente no sé cómo implementarlos y no tengo tiempo para aprenderlo ahora mismo. –

11

Aquí hay dos problemas: las construcciones predeterminadas tienen el formato MyClass c;; con paréntesis, parece una declaración de función para el compilador.

El otro problema es que Container<Interface> es simplemente un tipo diferente entonces Container<Foo> - que podría hacer la siguiente vez que trajeran polimorfismo:

Bar::Bar(const Container<Interface*>&) {} 

Container<Interface*> container; 
container.push_back(new Foo); 
Bar* temp = new Bar(container); 

O, por supuesto, usted podría hacer Bar o su constructor una plantilla como Kornel ha mostrado.

Si realmente quieres algo de polimorfismo en tiempo de compilación de tipo seguro, usted podría utilizar Boost.TypeTraitsis_base_of o algún equivalente:

template<class T> 
Bar::Bar(const Container<T>& c) { 
    BOOST_STATIC_ASSERT((boost::is_base_of<Interface, T>::value)); 
    // ... will give a compile time error if T doesn't 
    // inherit from Interface 
} 
+0

Gracias. Voy a intentarlo. –

+0

+1: pensándolo bien, esta sería una solución mucho mejor. –

+0

Eso es realmente bueno y exactamente lo que necesito. No tendré que cambiar una gran cantidad de código ya implementado. Gracias de nuevo. –

-1

contenedor es un contenedor de objetos Foo no un contenedor de objetos de interfaz

Y tampoco puede ser polimórfico, pueden ser indicadores de cosas, pero no los objetos en sí mismos. ¿Qué tan grande sería de las ranuras en el recipiente tiene que ser para el envase si se puede poner cualquier cosa derivada de interfaz en ella

necesita

container<Interface*> 

o mejor

container<shared_ptr<Interface> > 
+1

@ pm100, shared_ptr ? ¿Alguna vez has usado un shared_ptr? –

+1

@kornel podría ser un error tipográfico ... – msi

+0

@msiemeri - cierto, pero tenga en cuenta que el resto de la respuesta supone que un contenedor almacena valores, mientras que en realidad podría almacenar la interfaz * –

2

Es posible crear un árbol de herencia para contenedores, que refleje el árbol de herencia de los datos. Si usted tiene los siguientes datos:

class Interface { 
public: 
    virtual ~Interface() 
     {} 
    virtual void print() = 0; 
}; 

class Number : public Interface { 
public: 
    Number(int value) : x(value) 
     {} 
    int get() const 
     { return x; } 
    void print() 
     { std::printf("%d\n", get()); }; 
private: 
    int x; 
}; 

class String : public Interface { 
public: 
    String(const std::string & value) : x(value) 
     {} 
    const std::string &get() const 
     { return x; } 
    void print() 
     { std::printf("%s\n", get().c_str()); } 
private: 
    std::string x; 
}; 

También podría tener los siguientes contenedores:

class GenericContainer { 
public: 
    GenericContainer() 
     {} 
    ~GenericContainer() 
     { v.clear(); } 

    virtual void add(Interface &obj) 
     { v.push_back(&obj); } 
    Interface &get(unsigned int i) 
     { return *v[ i ]; } 
    unsigned int size() const 
     { return v.size(); } 
private: 
    std::vector<Interface *> v; 
}; 

class NumericContainer : public GenericContainer { 
public: 
    virtual void add(Number &obj) 
     { GenericContainer::add(obj); } 
    Number &get(unsigned int i) 
     { return (Number &) GenericContainer::get(i); } 
}; 

class TextContainer : public GenericContainer { 
public: 
    virtual void add(String &obj) 
     { GenericContainer::add(obj); } 
    String &get(unsigned int i) 
     { return (String &) GenericContainer::get(i); } 
}; 

Este no es el mejor código de realización; es solo para dar una idea. El único problema con este enfoque es que cada vez que agrega una nueva clase de datos, también debe crear un nuevo contenedor. Aparte de eso, tienes polimorfismo "trabajando de nuevo". Puede ser específico o general:

void print(GenericContainer & x) 
{ 
    for(unsigned int i = 0; i < x.size(); ++i) { 
     x.get(i).print(); 
    } 
} 

void printNumbers(NumericContainer & x) 
{ 
    for(unsigned int i = 0; i < x.size(); ++i) { 
     printf("Number: "); 
     x.get(i).print(); 
    } 
} 

int main() 
{ 
    TextContainer strContainer; 
    NumericContainer numContainer; 
    Number n(345); 
    String s("Hello"); 

    numContainer.add(n); 
    strContainer.add(s); 

    print(strContainer); 
    print(numContainer); 
    printNumbers(numContainer); 
} 
2

Propongo la siguiente solución, que emplea una función de plantilla. Aunque el ejemplo usa la QList de Qt, nada impide que la solución se transponga directamente a cualquier otro contenedor.

template <class D, class B> // D (Derived) inherits from B (Base) 
QList<B> toBaseList(QList<D> derivedList) 
{ 
    QList<B> baseList; 
    for (int i = 0; i < derivedList.size(); ++i) { 
     baseList.append(derivedList[i]); 
    } 
    return baseList; 
} 

Pros:

  • generales
  • de tipo seguro
  • bastante eficiente si los artículos son punteros o alguna otra forma barata copy-construible elementos (tales como clases de Qt implícitamente compartidos)

Contras:

  • requiere la creación de un nuevo contenedor, en lugar de permitir la reutilización de la original
  • implica algo de memoria y procesador de cabeza tanto para crear y poblar el nuevo contenedor, que dependen en gran medida el costo de la copia -constructor