web-dev-qa-db-fra.com

Comment réaliser une "fonction de modèle virtuel" en C++

tout d'abord: j'ai lu et je sais maintenant qu'une fonction de membre de modèle virtuel n'est pas (encore?) possible en C++. Une solution de contournement consisterait à faire de la classe un modèle, puis à utiliser l'argument modèle également dans la fonction membre.

Mais dans le contexte de la programmation orientée objet, je trouve que l'exemple ci-dessous ne serait pas très "naturel" si la classe était en réalité un modèle. Veuillez noter que le code ne fonctionne pas, mais le gcc-4.3.4 indique: 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;
}

Donc, créer un "poisson <montant> foo" est un peu étrange. Cependant, il me semble souhaitable de fournir une quantité arbitraire de nourriture à manger pour chaque animal.

Ainsi, je cherche une solution sur la façon de réaliser quelque chose comme

Fish bar;
bar.eat( SomeAmount food );

Cela devient particulièrement utile lorsque vous regardez la boucle for. On pourrait vouloir donner une quantité spécifique (FoodAmount) à tous les animaux (via eat () et bind1st () eg), cela ne pourrait pas se faire aussi facilement, bien que je trouve cela très intuitif (et donc dans une certaine mesure "naturel). Alors que certains voudront peut-être faire valoir maintenant que cela est dû au caractère" uniforme "d'un vecteur, je pense/souhaite qu'il soit possible d'y parvenir et j'aimerais vraiment savoir comment, me laisse perplexe depuis un certain temps maintenant ...

[MODIFIER]

Pour peut-être clarifier la motivation de ma question, je souhaite programmer une classe d'exportateur et laisser différentes classes plus spécialisées en découler. Alors que la classe d'exportateur de niveau supérieur sert généralement uniquement à des fins esthétiques/structurelles, une classe GraphExporter est dérivée, qui devrait à nouveau servir de classe de base pour une exportation encore plus spécialisée. Cependant, à l'instar de l'exemple Animal, j'aimerais pouvoir définir GraphExporter * même sur des classes spécialisées/dérivées (par exemple sur SpecialGraphExplorer), mais lors de l'appel de "write (out_file)", la fonction membre appropriée pour SpecialGraphExporter doit être appelée de GraphExporter :: write (out_file).

Cela clarifie peut-être ma situation et mes intentions.

Meilleur,

Ombre

34
Shadow

De toute évidence, les modèles de fonction de membre virtuel ne sont pas autorisés et ne pourraient pas être réalisés même théoriquement. Pour créer la table virtuelle d'une classe de base, il faut un nombre fini d'entrées de pointeur de fonction virtuel. Un modèle de fonction admettrait une quantité indéfinie de "surcharges" (c'est-à-dire d'instanciations).

Théoriquement, un langage (tel que C++) pourrait autoriser les modèles de fonction de membre virtuel s'il disposait d'un mécanisme permettant de spécifier la liste réelle (finie) d'instanciations. C++ a ce mécanisme (c'est-à-dire des instanciations de modèles explicites), donc je suppose qu'il serait possible de le faire dans un nouveau standard C++ (bien que je ne sache pas quel problème cela impliquerait pour les fournisseurs de compilateurs d'implémenter cette fonctionnalité). Mais, ce n’est qu’une discussion théorique, dans la pratique, ceci n’est tout simplement pas autorisé. Le fait demeure que vous devez définir le nombre de fonctions virtuelles (pas de modèles autorisés).

Bien entendu, cela ne signifie pas que le modèle de classe ne peut pas avoir de fonctions virtuelles, ni que les fonctions virtuelles ne peuvent pas appeler de modèles de fonction. Il existe donc de nombreuses solutions dans ce sens (comme le modèle de visiteur ou d’autres schémas).

Une solution qui, je pense, sert votre objectif (même s’il est difficile à comprendre) avec élégance est la suivante (qui est fondamentalement un modèle de visiteur):

#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;
};

La solution ci-dessus est une solution judicieuse, car elle vous permet de définir un nombre fini de surcharges que vous souhaitez à un seul endroit (dans le modèle de classe Eater_impl) et que tout ce dont vous avez besoin dans la classe dérivée est un modèle de fonction (et éventuellement cas spéciaux). Il y a bien sûr un peu de surcharge, mais je suppose qu'un peu plus de réflexion pourrait être mise en œuvre pour réduire la surcharge (stockage de référence supplémentaire et allocation dynamique de Eater_impl). Je suppose que le modèle de modèle curieusement récurrent pourrait probablement être utilisé d’une manière ou d’une autre à cette fin.

11
Mikael Persson

Je pense que le modèle de visiteur peut être une solution.

METTRE À JOUR

J'ai fini mon exemple:

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

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 AMOUNT>
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<Animal> TAnimal;
    std::vector<TAnimal> animals;
    animals.Push_back(TAnimal(new Animal()));
    animals.Push_back(TAnimal(new Wolf()));
    animals.Push_back(TAnimal(new Fish()));

    AmountVisitor<int> amount(10);

    for (std::vector<TAnimal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->Accept(amount);
    }

    return 0;
}

ceci imprime:

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

Selon le message de Mikael, j'ai créé une autre branche, utilisant le CRTP et suivant le style utilisé par Eigen pour utiliser derived() comme référence explicite à une sous-classe:

// 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;
}

Sortie:

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)

Cet extrait peut être trouvé dans le code source ici: repro: c808ef0: cpp_quick/virtual_template.cc

3
eacousineau

La fonction de modèle virtuel n'est pas autorisée. Cependant, vous pouvez utiliser l'un OR l'autre ici.

Vous pouvez créer une interface en utilisant des méthodes virtuelles et implémenter vos différents animaux en termes d’interface alimentaire. (c'est-à-dire PIMPL)

Intuitivement moins humain serait d'avoir une fonction de modèle non membre non membre comme une fonction libre qui pourrait prendre une référence constante modèle à n'importe quel animal et lui faire manger en conséquence.

Pour mémoire, vous n'avez pas besoin de modèles ici. La méthode abstraite virtuelle pure sur la classe de base est suffisante pour forcer et interfacer où tous les animaux doivent manger et définir comment ils le font avec une substitution, fournir un virtuel régulier serait suffisant pour dire que tous les animaux peuvent manger, mais s'ils n'ont pas manière spécifique alors ils peuvent utiliser cette manière par défaut.

2
AJG85

Vous pouvez créer une classe de modèle avec une fonction virtuelle et l'implémenter dans la classe dérivée sans utiliser de modèle de la manière suivante:

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);

malheureusement, je n'ai pas trouvé le moyen de créer une fonction virtuelle avec des paramètres de modèle sans déclarer la classe en tant que modèle et le type de modèle sur la classe déviée.

2
user3103989

J'ai copié et modifié mon code, il devrait maintenant fonctionner exactement comme vous le souhaitez:

        #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;
        }

Dans votre scénario, vous essayez de mélanger le polymorphisme de compilation avec le polymorphisme d'exécution, mais cela ne peut pas être fait dans cette "direction".

Essentiel, votre argument de modèle AMOUNT représente une interface attendue pour le type à implémenter en fonction de l'union de toutes les opérations utilisées par chaque implémentation de eat. Si vous voulez créer un type abstrait qui déclare chacune de ces opérations en les rendant virtuelles si nécessaire, vous pouvez alors appeler manger avec différents types (ceux dérivés de votre interface AMOUNT). Et il se comporterait comme prévu.

0
nate