如何在C++中实现"virtual template function"

How to achieve "virtual template function" in C++

本文关键字:virtual template function 实现 C++      更新时间:2023-10-16

首先:我已经读过了,现在我知道在C++中虚拟模板成员函数是不可能的。一种解决方法是使类成为模板,然后在成员函数中也使用模板参数。

但在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<Amount>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(。

也许这会让我的处境和意图更加清晰。

最佳,

Shadow

经过思考,我认为这是经典的多方法要求,即一种基于多个参数的运行时类型进行调度的方法。相比之下,通常的虚拟函数是single dispatch(并且它们仅在this的类型上调度(。

参考以下内容:

  • Andrei Alexandrescu写了一篇关于在"现代C++设计"中使用泛型实现多方法的文章(C++的开创性部分?(
    • 第11章:"多方法"-它实现了基本的多方法,使它们成为对数的(使用有序的类型列表(,然后一直到恒定时间的多方法。非常强大的东西
  • 一篇代码项目文章似乎就有这样一个实现:
    • 不使用任何类型的类型转换(动态、静态、重新解释、常量或C样式(
    • 不使用RTTI
    • 不使用预处理器
    • 型式安全性强
    • 单独汇编
    • 多方法执行的恒定时间
    • 在多方法调用期间没有动态内存分配(通过new或malloc(
    • 不使用非标准库
    • 仅使用标准C++功能
  • C++开放方法编译器,Peter Pirkelbauer、Yuriy Solodkyy和Bjarne Stroustrup
  • 洛基图书馆有一个多人调度员
  • 维基百科用C++编写了一篇非常简单的文章,并举例说明了Multiple Dispatch

以下是维基百科文章中的"简单"方法供参考(越不简单的方法越适合大量的派生类型(:

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

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

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

我认为访问者模式可以成为一种解决方案。

更新

我完成了我的例子:

#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!

根据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)

此代码段可在以下源代码中找到:repo:c808ef0:cpp_quick/virtual_template.cc

不允许使用虚拟模板功能。但是,您可以在此处使用一个或另一个。

你可以使用虚拟方法制作一个界面,并在吃的界面上实现你的各种动物。(即PIMPL(

不太人性化的直觉是将非成员非朋友模板函数作为一个自由函数,它可以将模板化的常量引用到任何动物身上,并使它们相应地进食。

为了记录在案,您不需要此处的模板。基类上的纯虚拟抽象方法足以强制和接口所有动物必须吃的地方,并通过覆盖来定义它们是如何吃的,提供一个常规的虚拟方法就足以说明所有动物都可以吃,但如果它们没有特定的方式,则可以使用此默认方式。

您可以创建一个具有虚拟函数的模板类,并在派生类中实现函数,而无需使用模板,方法如下:

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类上声明该类为模板和它的模板类型

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

在您的场景中,您正试图将编译时多态性与运行时多态性混合在一起,但这不能在这个"方向"上完成。

重要的是,您的AMOUNT模板参数表示要实现的类型的预期接口,该接口基于eat的每个实现使用的所有操作的并集。如果你在哪里创建一个抽象类型,声明这些操作中的每一个,使它们在需要的地方成为虚拟的,那么你可以用不同的类型(从你的AMOUNT接口派生的(调用eat。它的行为会如预期的那样。

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

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

(2( 在O.O.p.中,您提出的问题,以及您试图通过使用模板来解决的问题,都可以通过使用继承来解决。

类的工作原理类似于模板,您可以通过添加新的东西来扩展,或者用指针、指向对象的指针(简称"引用"(和重写虚拟函数来替换类的东西。

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

干杯。