如何在C ++中实现“虚拟模板功能”

时间:2011-05-03 15:16:16

标签: c++ templates virtual

首先关闭:我已阅读并且我现在知道在C ++中虚拟模板成员函数不可能(但是?)。解决方法是使类成为模板,然后在成员函数中使用template-argument。

但是在OOP的上下文中,我发现如果该类实际上是一个模板,下面的例子将不会非常“自然”。请注意,代码实际上无效,但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&lt; Amount&gt; foo”有点奇怪。然而,我似乎希望为每只动物提供任意数量的食物。

因此,我正在寻找一个关于如何实现类似

的解决方案
Fish bar;
bar.eat( SomeAmount food );

在查看for循环时,这变得特别有用。有人可能想给所有不同的动物喂食一定量的食物(FoodAmount)(通过eat()和bind1st()例如),虽然我觉得这个非常直观(因此在一定程度上)虽然有些人可能想现在争辩说这是由于矢量的“统一”特征,但我认为/希望它应该可以实现这一目标,我真的想知道如何,因为这是让我困惑了一段时间......

[编辑]

为了澄清我的问题背后的动机,我想编写一个Exporter-class,让不同的,更专业的类派生出来。虽然顶层Exporter-class通常仅用于化妆品/结构目的,但派生了一个GraphExporter类,它应该再次作为基础类进行更加特殊的导出。但是,类似于Animal-example,我希望能够在专门的/派生类(例如,在SpecialGraphExplorer上)定义GraphExporter *,但是在调用“write(out_file)”时,它应该调用SpecialGraphExporter的相应成员函数GraphExporter :: write(out_file)。

也许这会让我的情况和意图更加清晰。

最佳,

9 个答案:

答案 0 :(得分:30)

经过一番思考后,我认为这是经典的多方法要求,即根据多个参数的运行时类型进行调度的方法。通常的虚拟函数比较single dispatch(并且它们仅针对 this 的类型进行调度。)

请参阅以下内容:

  • Andrei Alexandrescu在“现代C ++设计”中使用泛型实现多方法的文章(C ++的开创性内容?)
    • Chapter 11: "Multimethods" - 它实现了基本的多方法,使它们成为对数(使用有序的类型列表),然后一直到恒定时间的多方法。相当强大的东西!
  • 似乎只有这样一个实现的codeproject article
    • 不使用任何类型的类型转换(动态,静态,重新解释,const或C风格)
    • 不使用RTTI;
    • 不使用预处理器;
    • 强力型安全;
    • 单独编译;
    • 多方法执行的恒定时间;
    • 在multimethod调用期间没有动态内存分配(通过new或malloc);
    • 不使用非标准库;
    • 仅使用标准C ++功能。
  • C++ Open Method Compiler,Peter Pirkelbauer,Yuriy Solodkyy和Bjarne Stroustrup
  • Loki图书馆有A MultipleDispatcher
  • 维基百科有一个很好的simple write-up,其中包含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
        }
    }
}

答案 1 :(得分:12)

显然,虚拟成员函数模板是不允许的,即使在理论上也无法实现。要构建基类的虚拟表,需要有一定数量的虚函数指针条目。函数模板将允许无限量的“重载”(即实例化)。

从理论上讲,语言(如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的动态分配)。我猜,奇怪的重复出现的模板模式可能会以某种方式用于此目的。

答案 2 :(得分:9)

我认为访客模式可以解决。

更新

我完成了我的例子:

#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 :(得分:3)

根据Mikael的帖子,我使用CRTP并遵循Eigen的使用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

答案 4 :(得分:2)

不允许使用虚拟模板功能。但是你可以在这里使用一个或另一个。

您可以使用虚拟方法创建一个界面,并根据进食界面实现您的各种动物。 (即PIMPL)

较少人性化的是将非成员非朋友模板函数作为自由函数,可以对任何动物进行模板化的const引用并使它们相应地进食。

对于记录,您不需要模板。基类上的纯虚拟抽象方法足以强制和界面所有动物必须吃的东西,并通过覆盖定义他们如何这样做,提供一个常规虚拟足以说所有动物都可以吃,但如果他们没有具体方式然后他们可以使用这种默认方式。

答案 5 :(得分:2)

您可以使用虚函数创建模板类,并在不使用模板的情况下在派生类中实现该函数:

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

遗憾的是,我没有找到一种方法来创建一个带有模板参数的虚函数,而无需将该类声明为模板,并将其模板类型设置为已安装的类

答案 6 :(得分:1)

我已经复制了你的代码并对其进行了修改,所以现在它应该可以正常工作了:

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

答案 7 :(得分:0)

在您的场景中,您正在尝试将编译时多态性与运行时多态性混合在一起,但无法在此方向中完成#34;。

基本上,您的AMOUNT模板参数表示要根据每个eat实现使用的所有操作的并集实现的类型的预期接口。如果您在哪里创建一个抽象类型,声明每个操作使它们在需要的地方虚拟,那么您可以使用不同类型(从您的AMOUNT接口派生)调用eat。它的行为与预期一致。

答案 8 :(得分:-8)

我不使用模板,但我认为:

(1)您不能在类中使用模板,模板更像是全局类型或全局变量。

(2)在O.O.P.中,您提出的问题与您尝试使用模板解决的问题可以通过使用继承来解决。

类的工作类似于模板,你可以通过添加新东西来扩展,或者用指针替换类,指向对象的指针(A.K.A.“references”)和覆盖虚函数。

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

干杯。