Как добиться «функции виртуального шаблона» в C ++ - PullRequest
38 голосов
/ 03 мая 2011

сначала: я прочитал и теперь знаю, что функция-член виртуального шаблона невозможна (пока?) В C ++. Обходной путь должен был бы сделать класс шаблоном и затем использовать аргумент шаблона также в функции-члене.

Но в контексте ООП я обнаружил, что приведенный ниже пример не был бы очень "естественным", если бы класс был на самом деле шаблоном. Обратите внимание, что код на самом деле не работает, но gcc-4.3.4 сообщает: 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;
}

Так что создание "Fish foo" довольно странно. Тем не менее, мне кажется желательным обеспечить произвольное количество пищи для каждого животного.

Таким образом, я ищу решение о том, как достичь чего-то вроде

Fish bar;
bar.eat( SomeAmount food );

Это становится особенно полезным, если смотреть на цикл for. Можно хотеть кормить определенное количество (FoodAmount) всем различным животным (например, через eat () и bind1st ()), это не может быть сделано так легко, хотя я нахожу это очень интуитивным (и, следовательно, в некоторой степени «естественно». Хотя некоторые, возможно, захотят поспорить, что это связано с «единообразным» характером вектора, я думаю / хочу, чтобы это было возможно, и мне бы очень хотелось узнать, как это происходит, поскольку озадачивает меня довольно долго ...

[EDIT]

Чтобы, возможно, прояснить мотивы, лежащие в основе моего вопроса, я хочу запрограммировать класс Exporter, и пусть другие, более специализированные классы будут происходить из него. В то время как класс Exporter верхнего уровня, как правило, предназначен только для косметических / структурных целей, класс GraphExporter является производным, который должен снова служить базовым классом для еще более специализированного экспорта. Однако, как и в примере с Animal, я хотел бы иметь возможность определять GraphExporter * даже в специализированных / производных классах (например, в SpecialGraphExplorer), но при вызове «write (out_file)» вместо этого он должен вызывать соответствующую функцию-член для SpecialGraphExporter GraphExporter :: write (out_file).

Может быть, это прояснит мою ситуацию и намерения.

Best

Shadow

Ответы [ 9 ]

30 голосов
/ 03 октября 2011

Подумав, я понял, что это классическое требование multi-method , то есть метод, который отправляет на основе тип времени выполнения более одного параметра. Обычные виртуальные функции для сравнения single dispatch (и отправляют только по типу this).

См. Следующее:

  • Андрей Александреску написал (основополагающие биты для C ++?) О реализации мульти-методов с использованием обобщений в «Современном дизайне C ++».
    • Глава 11: «Мультиметоды» - он реализует базовые мульти-методы, делая их логарифмическими (используя упорядоченные списки типов), а затем переходя к мульти-методам с постоянным временем. Довольно мощный материал!
  • A статья codeproject , которая, кажется, имеет именно такую ​​реализацию:
    • не использовать приведение типов любого типа (динамическое, статическое, переинтерпретация, const или C-style)
    • нет использования RTTI;
    • без использования препроцессора;
    • сильный тип безопасности;
    • отдельная подборка;
    • постоянное время выполнения мультиметода;
    • нет динамического выделения памяти (через new или malloc) во время мультиметодного вызова;
    • не использовать нестандартные библиотеки;
    • используются только стандартные функции C ++.
  • C ++ Open Method Compiler , Питер Пиркельбауэр, Юрий Солодкий и Бьярн Страуструп
  • Библиотека Loki имеет MultipleDispatcher
  • В Википедии есть довольно симпатичная простая запись с примерами Multiple Dispatch в C ++.

Вот «простой» подход из статьи в Википедии для справки (менее простой подход лучше масштабируется для большего числа производных типов):

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

12 голосов
/ 03 мая 2011

Очевидно, что шаблоны виртуальных функций-членов недопустимы и не могут быть реализованы даже теоретически.Чтобы построить виртуальную таблицу базового класса, должно быть конечное число записей указателя виртуальной функции.Шаблон функции допускал бы неограниченное количество «перегрузок» (т.е. экземпляров).

Теоретически, язык (такой как C ++) мог бы разрешать шаблоны функций виртуальных членов, если бы у него был некоторый механизм для указания фактического (конечного)) список экземпляров.В C ++ этот механизм действительно есть (т. Е. В явных экземплярах шаблонов), поэтому я полагаю, что это возможно сделать в более новом стандарте C ++ (хотя я понятия не имею, какие проблемы могут возникнуть у поставщиков компиляторов для реализации этой функции).Но это только теоретическое обсуждение, на практике это просто не допускается.Факт остается фактом: нужно ограничить число виртуальных функций (шаблоны не допускаются).

Конечно, это не означает, что шаблон класса не может иметь виртуальные функции, а также не означает, что виртуальные функции не могутшаблоны функций вызова.Таким образом, существует множество решений в этом ключе (например, шаблон посетителя или другие схемы).

Одним из решений, которое, я думаю, служит вашей цели (хотя его трудно понять), элегантно является следующее (которое являетсяв основном шаблон посетителя):

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

Вышеприведенное решение является изящным решением, поскольку оно позволяет вам определить конечное число перегрузок, которые вы хотите получить только в одном месте (в шаблоне класса Eater_impl) и все, что вам нужнов производном классе есть шаблон функции (и, возможно, дополнительные перегрузки для особых случаев).Конечно, есть некоторые издержки, но я предполагаю, что можно было бы немного больше подумать, чтобы уменьшить их (дополнительное хранилище ссылок и динамическое выделение Eater_impl).Я предполагаю, что любопытно повторяющийся шаблонный шаблон, вероятно, мог бы быть использован для этой цели.

9 голосов
/ 03 мая 2011

Я думаю, что шаблон посетителя может быть решением.

UPDATE

Я закончил свой пример:

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

это печатает:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
3 голосов
/ 16 апреля 2017

Согласно сообщению Микаэля, я сделал еще один ответвление, используя CRTP и следуя стилю Эйгена, используя derived() для явной ссылки на подкласс:

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

Выход:

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)

Этот фрагмент может быть найден в источнике здесь: repro: c808ef0: cpp_quick / virtual_template.cc

2 голосов
/ 15 июля 2015

Вы можете создать шаблонный класс с виртуальной функцией и реализовать функцию в производном классе без использования шаблона следующим образом:

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

к сожалению, я не нашел способа создать виртуальныйфункция с параметрами шаблона без объявления класса как шаблона и его типа шаблона в классе Dervied

2 голосов
/ 03 мая 2011

Функция виртуального шаблона не допускается. Однако вы можете использовать один ИЛИ другой здесь.

Вы можете создать интерфейс, используя виртуальные методы, и реализовать своих различных животных с точки зрения наличия пищевого интерфейса. (т.е. PIMPL)

Менее интуитивно понятный человек будет иметь функцию шаблона, не являющуюся другом, не являющимся другом, в качестве бесплатной функции, которая может принимать шаблонную константную ссылку на любое животное и заставлять его есть соответственно.

Для записи вам здесь не нужны шаблоны. Чистого виртуального абстрактного метода в базовом классе достаточно, чтобы заставить и взаимодействовать, где все животные должны есть, и определить, как они это делают с переопределением, при условии, что обычного виртуального будет достаточно, чтобы сказать, что все животные могут есть, но если у них нет конкретным образом, тогда они могут использовать этот путь по умолчанию.

1 голос
/ 05 августа 2016

Я скопировал ваш код и изменил его, поэтому теперь он должен работать именно так, как вы хотите:

        #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;
        }
0 голосов
/ 05 августа 2016

В вашем сценарии вы пытаетесь смешать полиморфизм времени компиляции с полиморфизмом времени выполнения, но это не может быть сделано в этом «направлении».

Существенно, ваш аргумент шаблона AMOUNT представляет собой ожидаемый интерфейс для реализуемого типа на основе объединения всех операций, которые использует каждая реализация eat. Если вам нужно создать абстрактный тип, который объявляет каждую из этих операций, делая их виртуальными, где это необходимо, то вы можете вызывать eat с различными типами (производными от вашего интерфейса AMOUNT). И он будет вести себя как ожидалось.

0 голосов
/ 03 мая 2011

Я не работаю с шаблонами, но думаю:

(1) Вы не можете использовать шаблоны внутри класса, шаблоны больше похожи на глобальные типы или глобальные переменные.

(2) В ООП та же проблема, которую вы представляете, и которую вы пытаетесь решить с помощью шаблонов, может быть решена с помощью наследования.

Классы работают аналогично шаблонам, вы можете расширить их, добавив новые элементы, или заменитьвещи классов с указателями, указателями на объекты («ссылки» AKA) и переопределяющими виртуальными функциями.

#include <iostream>

struct Animal {
    virtual void eat(int amount ) {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual ~Animal() { }
};

#if 0

// example 1
struct Wolf : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a wolf!" << std::endl;
    }
};

struct Fish : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a fish!" << std::endl;
    }
};

#else

// example 2

struct AnimalFood {
    virtual int readAmount() { return 5; }

    virtual void showName() {
        std::cout << "I'm generic animal food" << std::endl;
    }
};

struct PredatorFood : AnimalFood {
    virtual int readAmount() { return 500; }

    virtual void showName() {
        std::cout << "I'm food for a predator" << std::endl;
    }
};


struct Fish : Animal {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 50) {
            std::cout << "OK food, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "too much food, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Shark : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 250) {
            std::cout << "too litle food for a shark, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Wolf : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 150) {
            std::cout << "too litle food for a wolf, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

#endif

int main() {
    // find animals
    Wolf* loneWolf = new Wolf();
    Fish* goldenFish = new Fish();
    Shark* sharky = new Shark();

    // prepare food
    AnimalFood* genericFood = new AnimalFood();
    PredatorFood* bigAnimalFood = new PredatorFood();

    // give food to animals
    loneWolf->eat(genericFood);
    loneWolf->eat(bigAnimalFood);

    goldenFish->eat(genericFood);
    goldenFish->eat(bigAnimalFood);

    sharky->eat(genericFood);
    sharky->eat(bigAnimalFood);

    delete bigAnimalFood;
    delete genericFood;

    delete sharky;
    delete goldenFish;
    delete loneWolf;
}

Приветствия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...