Cómo lograr “función de plantilla virtual” en C ++

primero: he leído y ahora sé que una función de miembro de plantilla virtual no es (¿todavía?) posible en C ++. Una solución alternativa sería convertir la clase en una plantilla y luego usar la plantilla-argumento también en la función miembro.

Pero en el contexto de OOP, encuentro que el siguiente ejemplo no sería muy “natural” si la clase fuera realmente una plantilla. Tenga en cuenta que el código en realidad no está funcionando, pero el gcc-4.3.4 informa: error: templates may not be 'virtual'

 #include  #include  class Animal { public: template virtual void eat( AMOUNT amount ) const { std::cout << "I eat like a generic Animal." << std::endl; } virtual ~Animal() { } }; class Wolf : public Animal { public: template void eat( AMOUNT amount) const { std::cout << "I eat like a wolf!" << std::endl; } virtual ~Wolf() { } }; class Fish : public Animal { public: template void eat( AMOUNT amount) const { std::cout << "I eat like a fish!" << std::endl; } virtual ~Fish() { } }; class GoldFish : public Fish { public: template void eat( AMOUNT amount) const { std::cout << "I eat like a goldfish!" << std::endl; } virtual ~GoldFish() { } }; class OtherAnimal : public Animal { virtual ~OtherAnimal() { } }; int main() { std::vector animals; animals.push_back(new Animal()); animals.push_back(new Wolf()); animals.push_back(new Fish()); animals.push_back(new GoldFish()); animals.push_back(new OtherAnimal()); for (std::vector::const_iterator it = animals.begin(); it != animals.end(); ++it) { (*it)->eat(); delete *it; } return 0; } 

Entonces crear un “Fish foo” es algo extraño. Sin embargo, me parece conveniente proporcionar una cantidad arbitraria de alimentos para cada animal.

Por lo tanto, estoy buscando una solución sobre cómo lograr algo así como

 Fish bar; bar.eat( SomeAmount food ); 

Esto se vuelve particularmente útil al mirar el for-loop. A uno le gustaría alimentar una cantidad específica (FoodAmount) a todos los diferentes animales (a través de eat () y bind1st () por ejemplo), no podría hacerse tan fácilmente, aunque me pareció muy intuitivo (y hasta cierto punto) “natural”. Aunque algunos quieran argumentar ahora que esto se debe al carácter “uniforme” de un vector, creo / desearía que fuera posible lograrlo y realmente me gustaría saber cómo, ya que es me desconcertó por bastante tiempo ahora …

[EDITAR]

Para aclarar tal vez la motivación detrás de mi pregunta, quiero progtwigr una clase Exportador y dejar derivar clases diferentes y más especializadas. Si bien la clase exportadora de nivel superior generalmente solo tiene fines cosméticos / estructurales, se deriva una clase GraphExporter, que nuevamente debería servir como una clase base para exportar aún más specialzed. Sin embargo, similar al Animal-example, me gustaría poder definir GraphExporter * incluso en clases especializadas / derivadas (por ejemplo, en SpecialGraphExplorer) pero al llamar a “write (out_file)”, debería llamar a la función miembro correspondiente para SpecialGraphExporter en su lugar de GraphExporter :: write (out_file).

Tal vez esto hace que mi situación e intenciones sean más claras.

Mejor,

Sombra

Después de pensar un poco, reconocí esto como el clásico requisito de múltiples métodos , es decir, un método que se distribuye en función del tipo de tiempo de ejecución de más de un parámetro. Las funciones virtuales usuales son un single dispatch en comparación (y se distribuyen solo en el tipo de this ).

Consulte lo siguiente:

  • Andrei Alexandrescu ha escrito (los bits fundamentales para C ++?) Sobre la implementación de métodos múltiples utilizando generics en ‘Diseño moderno de C ++’
    • Capítulo 11: “Multimétodos” : implementa métodos múltiples básicos, haciéndolos logarítmicos (usando listas de tipos ordenadas) y luego yendo hasta los métodos múltiples de tiempo constante. ¡Algo bastante poderoso!
  • Un artículo del proyecto de código que parece tener tal implementación:
    • no se usan moldes tipo de ningún tipo (dynamic, estático, reinterpretado, const o estilo C)
    • no uso de RTTI;
    • sin uso del preprocesador;
    • seguridad de tipo fuerte;
    • comstackción separada;
    • tiempo constante de ejecución multimétodo;
    • no hay asignación dinámica de memoria (a través de new o malloc) durante la llamada multimétodo;
    • no uso de bibliotecas no estándares;
    • solo se usan las características estándar de C ++.
  • Comstackdor de métodos abiertos de C ++ , Peter Pirkelbauer, Yuriy Solodkyy y Bjarne Stroustrup
  • La biblioteca Loki tiene un MultipleDispatcher
  • Wikipedia tiene una buena redacción simple y agradable con ejemplos de Despacho múltiple en C ++.

Aquí está el enfoque ‘simple’ del artículo de wikipedia para referencia (el enfoque menos simple escalas mejor para un mayor número de tipos derivados):

 // Example using run time type comparison via dynamic_cast struct Thing { virtual void collideWith(Thing& other) = 0; } struct Asteroid : Thing { void collideWith(Thing& other) { // dynamic_cast to a pointer type returns NULL if the cast fails // (dynamic_cast to a reference type would throw an exception on failure) if (Asteroid* asteroid = dynamic_cast(&other)) { // handle Asteroid-Asteroid collision } else if (Spaceship* spaceship = dynamic_cast(&other)) { // handle Asteroid-Spaceship collision } else { // default collision handling here } } } struct Spaceship : Thing { void collideWith(Thing& other) { if (Asteroid* asteroid = dynamic_cast(&other)) { // handle Spaceship-Asteroid collision } else if (Spaceship* spaceship = dynamic_cast(&other)) { // handle Spaceship-Spaceship collision } else { // default collision handling here } } } 

Obviamente, las plantillas de funciones de miembros virtuales no están permitidas y no podrían realizarse incluso teóricamente. Para construir una tabla virtual de clase base, debe haber un número finito de entradas de puntero a función virtual. Una plantilla de función admitiría una cantidad indefinida de “sobrecargas” (es decir, instancias).

Teóricamente hablando, un lenguaje (como C ++) podría permitir plantillas de funciones de miembros virtuales si tuviera algún mecanismo para especificar la lista real (finita) de instancias. C ++ tiene ese mecanismo (es decir, instancias de plantillas explícitas), así que supongo que podría ser posible hacerlo en un estándar C ++ más nuevo (aunque no tengo idea de qué problemas implicaría para los proveedores del comstackdor implementar esta característica). Pero, eso es solo una discusión teórica, en la práctica, esto simplemente no está permitido. El hecho es que tienes que hacer que el número de funciones virtuales sea finito (no se permiten plantillas).

Por supuesto, eso no significa que la plantilla de la clase no pueda tener funciones virtuales, ni tampoco significa que las funciones virtuales no puedan llamar a las plantillas de funciones. Entonces, hay muchas soluciones en esa línea (como el patrón Visitor u otros esquemas).

Una solución que, creo, sirve para su propósito (aunque es difícil de comprender) elegantemente es la siguiente (que básicamente es un patrón de visitante):

 #include  #include  struct Eater { virtual void operator()(int amount) const = 0; virtual void operator()(double amount) const = 0; }; template  struct Eater_impl : Eater { EaterType& data; Eater_impl(EaterType& aData) : data(aData) { }; virtual void operator()(int amount) const { data.eat_impl(amount); }; virtual void operator()(double amount) const { data.eat_impl(amount); }; }; class Animal { protected: Animal(Eater& aEat) : eat(aEat) { }; public: Eater& eat; virtual ~Animal() { delete &eat; }; }; class Wolf : public Animal { private: template< class AMOUNT > void eat_impl( AMOUNT amount) const { std::cout << "I eat like a wolf!" << std::endl; } public: friend struct Eater_impl; Wolf() : Animal(*(new Eater_impl(*this))) { }; virtual ~Wolf() { }; }; class Fish : public Animal { private: template< class AMOUNT > void eat_impl( AMOUNT amount) const { std::cout << "I eat like a fish!" << std::endl; } public: friend struct Eater_impl; Fish() : Animal(*(new Eater_impl(*this))) { }; virtual ~Fish() { }; }; int main() { std::vector animals; animals.push_back(new Wolf()); animals.push_back(new Fish()); for (std::vector::const_iterator it = animals.begin(); it != animals.end(); ++it) { (*it)->eat(int(0)); (*it)->eat(double(0.0)); delete *it; }; return 0; }; 

Lo anterior es una solución ordenada porque le permite definir un número finito de sobrecargas que desea en un solo lugar (en la plantilla de clase Eater_impl) y todo lo que necesita en la clase derivada es una plantilla de función (y posiblemente sobrecargas adicionales, para casos especiales). Hay, por supuesto, un poco de sobrecarga, pero creo que se podría pensar un poco más en reducir la sobrecarga (almacenamiento de referencia adicional y asignación dinámica de Eater_impl). Supongo que el patrón de plantilla curiosamente recurrente probablemente podría emplearse de alguna manera para este fin.

Creo que el patrón de visitante puede ser una solución.

ACTUALIZAR

Terminé mi ejemplo:

 #include  #include  #include  class Animal; class Wolf; class Fish; class Visitor { public: virtual void visit(const Animal& p_animal) const = 0; virtual void visit(const Wolf& p_animal) const = 0; virtual void visit(const Fish& p_animal) const = 0; }; template class AmountVisitor : public Visitor { public: AmountVisitor(AMOUNT p_amount) : m_amount(p_amount) {} virtual void visit(const Animal& p_animal) const { std::cout << "I eat like a generic Animal." << std::endl; } virtual void visit(const Wolf& p_animal) const { std::cout << "I eat like a wolf!" << std::endl; } virtual void visit(const Fish& p_animal) const { std::cout << "I eat like a fish!" << std::endl; } AMOUNT m_amount; }; class Animal { public: virtual void Accept(const Visitor& p_visitor) const { p_visitor.visit(*this); } virtual ~Animal() { } }; class Wolf : public Animal { public: virtual void Accept(const Visitor& p_visitor) const { p_visitor.visit(*this); } }; class Fish : public Animal { public: virtual void Accept(const Visitor& p_visitor) const { p_visitor.visit(*this); } }; int main() { typedef boost::shared_ptr TAnimal; std::vector animals; animals.push_back(TAnimal(new Animal())); animals.push_back(TAnimal(new Wolf())); animals.push_back(TAnimal(new Fish())); AmountVisitor amount(10); for (std::vector::const_iterator it = animals.begin(); it != animals.end(); ++it) { (*it)->Accept(amount); } return 0; } 

esto imprime:

 I eat like a generic Animal. I eat like a wolf! I eat like a fish! 

La función de plantilla virtual no está permitida. Sin embargo, puede usar uno u otro aquí.

Podría hacer una interfaz usando métodos virtuales e implementar sus diversos animales en términos de tener una interfaz de alimentación. (es decir, PIMPL)

Menos intuitivo sería tener una función de plantilla no miembro sin amigos como una función gratuita que podría tomar como referencia referencia constual a cualquier animal y hacer que coman en consecuencia.

Para el registro, no necesita plantillas aquí. El método abstracto virtual puro en la clase base es suficiente para forzar e interconectar donde todos los animales deben comer y definir cómo lo hacen con una anulación, proporcionando una virtual regular sería suficiente para decir que todos los animales pueden comer, pero si no tienen un manera específica, entonces pueden usar esta forma predeterminada.

Puede crear una clase de plantilla con función virtual e implementar la función en la clase derivada sin usar la plantilla de la siguiente manera:

 ah: template  class A { public: A() { qDebug() << "a"; } virtual A* Func(T _template) { return new A;} }; bh: class B : public A { public: B(); virtual A* Func(int _template) { return new B;} }; and the function CTOR and call A* a1=new B; int x=1; a1->Func(x); 

desafortunadamente no he encontrado una manera de crear una función virtual con parámetros de plantilla sin declarar la clase como una plantilla y su tipo de plantilla en la clase dervied