Pregunta 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 funciona, pero el gcc-4.3.4 informa: error: templates may not be ‘virtual’

#include <iostream>
#include <vector>

class Animal {
    public:
        template< class AMOUNT >
        virtual void eat( AMOUNT amount ) const { 
            std::cout << "I eat like a generic Animal." << std::endl; 
        }
        virtual ~Animal() { 
        }
};

class Wolf : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a wolf!" << std::endl; 
        }
        virtual ~Wolf() { 
        }
};

class Fish : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a fish!" << std::endl; 
        }
        virtual ~Fish() { 
        }
};

class GoldFish : public Fish {
    public:
        template< class AMOUNT >
        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<Animal*> 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<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->eat();
        delete *it;
    }

    return 0;
}

Entonces crear un "Fish <Amount> 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 programar 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


32
2018-05-03 15:16


origen


Respuestas:


Después de pensar un poco, reconocí esto como el clásico multi-método requisito, es decir, un método que se distribuye según el tipo de tiempo de ejecución de más de un parámetro Las funciones virtuales usuales son single dispatch en comparación (y envían en el tipo de this solamente).

Consulte lo siguiente:

  • Andrei Alexandrescu ha escrito (los bits fundamentales para C ++?) Sobre la implementación de métodos múltiples utilizando genéricos 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 (dinámico, estático, reinterpretado, const o estilo C)
    • no uso de RTTI;
    • sin uso del preprocesador;
    • seguridad de tipo fuerte;
    • compilació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 ++.
  • C ++ Open Method Compiler, Peter Pirkelbauer, Yuriy Solodkyy y Bjarne Stroustrup
  • La Biblioteca Loki tiene Un MultipleDispatcher
  • Wikipedia tiene bastante buen redacción simple con ejemplos en 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<Asteroid*>(&other)) {
            // handle Asteroid-Asteroid collision
        } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
            // handle Asteroid-Spaceship collision
        } else {
            // default collision handling here
        }
    }
}

struct Spaceship : Thing {
    void collideWith(Thing& other) {
        if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
            // handle Spaceship-Asteroid collision
        } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
            // handle Spaceship-Spaceship collision
        } else {
            // default collision handling here
        }
    }
}


26
2017-10-02 20:29



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 nuevo estándar de C ++ (aunque no tengo idea de qué problemas implicaría para los proveedores de compiladores 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 <iostream>
#include <vector>

struct Eater { 
  virtual void operator()(int amount) const = 0;
  virtual void operator()(double amount) const = 0;
};

template <typename EaterType>
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>;        
    Wolf() : Animal(*(new Eater_impl<Wolf>(*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>;
    Fish() : Animal(*(new Eater_impl<Fish>(*this))) { };
    virtual ~Fish() { };
};

int main() {
  std::vector<Animal*> animals;
  animals.push_back(new Wolf());
  animals.push_back(new Fish());

  for (std::vector<Animal*>::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.


11
2018-05-03 16:27



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

ACTUALIZAR


8
2018-05-03 15:55



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.


2
2018-05-03 15:32



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:

a.h:

template <class T>
class A
{
public:
    A() { qDebug() << "a"; }

    virtual A* Func(T _template) { return new A;}
};


b.h:

class B : public A<int>
{
public:
    B();
    virtual A* Func(int _template) { return new B;}
};


and the function CTOR and call 

  A<int>* 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


2
2017-07-15 10:56



He copiado tu código y lo he modificado, por lo que ahora debería funcionar exactamente como lo deseas:

        #include <iostream>
        #include <vector>

        //defined new enum type
        enum AnimalEnum
        {
           animal,
           wolf,
           fish,
           goldfish,
           other
        };

        //forward declarations
        class Wolf;
        class Fish;
        class GoldFish;
        class OtherAnimal;

        class Animal {
            private:
            AnimalEnum who_really_am_I;
            void* animal_ptr;
            public:
                //declared new constructors overloads for each type of animal
                Animal(const Animal&);
                Animal(const Wolf&);
                Animal(const Fish&);
                Animal(const GoldFish&);
                Animal(const OtherAnimal&);
                template< class AMOUNT >
                /*removed the virtual keyword*/ void eat( AMOUNT amount ) const { 
                    switch (this->who_really_am_I)
                    {
                       case AnimalEnum::other: //You defined OtherAnimal so that it doesn't override the eat action, so it will uses it's Animal's eat
                       case AnimalEnum::animal: std::cout << "I eat like a generic Animal." << std::endl; break;
                       case AnimalEnum::wolf: ((Wolf*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::fish: ((Fish*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::goldfish: ((GoldFish*)this->animal_ptr)->eat(amount) break;
                    }
                }
                void DeleteMemory() { delete this->animal_ptr; }
                virtual ~Animal() { 
                   //there you can choose if whether or not to delete "animal_ptr" here if you want or not
                }
        };

        class Wolf : public Animal {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a wolf!" << std::endl; 
                }
                virtual ~Wolf() { 
                }
        };

        class Fish : public Animal {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a fish!" << std::endl; 
                }
                virtual ~Fish() { 
                }
        };

        class GoldFish : public Fish {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a goldfish!" << std::endl; 
                }
                virtual ~GoldFish() { 
                }
        };

        class OtherAnimal : public Animal {
                //OtherAnimal constructors must be defined here as Animal's constructors
                OtherAnimal(const Animal& a) : Animal(a) {}
                OtherAnimal(const Wolf& w) : Animal(w) {}
                OtherAnimal(const Fish& f) : Animal(f) {}
                OtherAnimal(const GoldFish& g) : Animal(g) {}
                OtherAnimal(const OtherAnimal& o) : Animal(o) {}
                virtual ~OtherAnimal() { 
                }
        };
        //OtherAnimal will be useful only if it has it's own actions and members, because if not, typedef Animal OtherAnimal or using OtherAnimal = Animal can be used, and it can be removed from above declarations and below definitions

//Here are the definitions of Animal constructors that were declared above/before:    
        Animal::Animal(const Animal& a) : who_really_am_I(AnimalEnum::animal), animal_ptr(nullptr) {}

        Animal::Animal(const Wolf& w) : who_really_am_I(AnimalEnum::wolf), animal_ptr(new Wolf(w)) {}

        Animal::Animal(const Fish& f) : who_really_am_I(AnimalEnum::fish), animal_ptr(new Fish(f)) {}

        Animal::Animal(const GoldFish& g) : who_really_am_I(AnimalEnum::goldfish), animal_ptr(new GoldFish(g)) {}

        Animal::Animal(const OtherAnimal& o) :
    who_really_am_I(AnimalEnum::other), animal_ptr(new OtherAnimal(o)) {}

        int main() {
            std::vector<Animal> animals;
            animals.push_back(Animal());
            animals.push_back(Wolf()); //Wolf is converted to Animal via constructor
            animals.push_back(Fish()); //Fish is converted to Animal via constructor
            animals.push_back(GoldFish()); //GoldFish is converted to Animal via constructor
            animals.push_back(OtherAnimal()); //OtherAnimal is converted to Animal via constructor

            for (std::vector<Animal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
                it->eat(); //this is Animal's eat that invokes other animals eat
                //delete *it; Now it should be:
                it->DeleteMemory();
            }
            animals.clear(); //All animals have been killed, and we don't want full vector of dead animals.

            return 0;
        }

1
2017-08-04 22:10



Según la publicación de Mikael, he hecho otra ramificación, usando el CRTP y siguiendo el estilo de Eigen de usar derived() para una referencia de subclase explícita:

// Adaptation of Visitor Pattern / CRTP from:
// http://stackoverflow.com/a/5872633/170413

#include <iostream>
using std::cout;
using std::endl;

class Base {
public:
  virtual void tpl(int x) = 0;
  virtual void tpl(double x) = 0;
};

// Generics for display
template<typename T>
struct trait {
  static inline const char* name() { return "T"; }
};
template<>
struct trait<int> {
  static inline const char* name() { return "int"; }
};
template<>
struct trait<double> {
  static inline const char* name() { return "double"; }
};

// Use CRTP for dispatch
// Also specify base type to allow for multiple generations
template<typename BaseType, typename DerivedType>
class BaseImpl : public BaseType {
public:
  void tpl(int x) override {
    derived()->tpl_impl(x);
  }
  void tpl(double x) override {
    derived()->tpl_impl(x);
  }
private:
  // Eigen-style
  inline DerivedType* derived() {
    return static_cast<DerivedType*>(this);
  }
  inline const DerivedType* derived() const {
    return static_cast<const DerivedType*>(this);
  }
};

// Have Child extend indirectly from Base
class Child : public BaseImpl<Base, Child> {
protected:
  friend class BaseImpl<Base, Child>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "Child::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};

// Have SubChild extend indirectly from Child
class SubChild : public BaseImpl<Child, SubChild> {
protected:
  friend class BaseImpl<Child, SubChild>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "SubChild::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};


template<typename BaseType>
void example(BaseType *p) {
  p->tpl(2);
  p->tpl(3.0);
}

int main() {
  Child c;
  SubChild sc;

  // Polymorphism works for Base as base type
  example<Base>(&c);
  example<Base>(&sc);
  // Polymorphism works for Child as base type
  example<Child>(&sc);
  return 0;
}

Salida:

Child::tpl_impl<int>(2)
Child::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)

Este fragmento se puede encontrar en la fuente aquí: repro: c808ef0: cpp_quick / virtual_template.cc


1
2018-04-16 17:56