如何在C++中实现"virtual template function"
How to achieve "virtual template function" in C++
首先:我已经读过了,现在我知道在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;
}
干杯。
- 表示"accepting anything for this template argument" C++概念的通配符
- C++核心准则 C35 对于接口类"A base class destructor should be either public and virtual, or protected and nonv
- 传递给std::function template的template参数究竟代表什么
- 在template中使用std::variant的template函数
- 当我从下面的代码中删除关键字 virtual 时,它可以正常工作,否则会出现错误。在这里"virtual"字的意义是什么?
- C++ template for QList
- C++ - 为什么这里需要'template'关键字?
- 使用"std::enable_if_t" "function template has already been defined"
- C++模板错误:"invalid explicitly-specified argument for template parameter"
- C++ class template
- 在"template"和函数声明之间使用:template<typename trait> using tr = base_trait<trait> void fn(tr::t
- 为什么编译器说"candidate template ignored: couldn't infer template argument 'InputIterator'"?
- 为什么在这种情况下我需要 .template
- 在基类中调用的 VIrtual 基函数,C++
- C++类:virtual和override,或者两者都没有
- std::span constructor, libcxx vs libstdc++, template vs non-
- 模板函数参数到模板函数的"candidate template ignored: could not match ..."
- MacOS 上的 LLVM - 标准文件 iosfwd 中未知类型名称'template'
- std::get like (partial) template specialization
- 如何在C++中实现"virtual template function"