Sha*_*dow 41 c++ virtual templates
首先:我已阅读并且我现在知道在C++中虚拟模板成员函数(但是?)是不可能的.解决方法是将类作为模板,然后在member-function中使用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;
}
Run Code Online (Sandbox Code Playgroud)
所以创造一个"Fish <Amount> foo"有点奇怪.然而,我似乎希望为每只动物提供任意数量的食物.
因此,我正在寻找一个如何实现类似的解决方案
Fish bar;
bar.eat( SomeAmount food );
Run Code Online (Sandbox Code Playgroud)
在查看for循环时,这变得特别有用.有人可能想给所有不同的动物喂食一定量的食物(FoodAmount)(通过eat()和bind1st()例如),虽然我觉得这个非常直观(因此在一定程度上)虽然有些人可能想现在争辩说这是由于矢量的"统一"特征,但我认为/希望能够实现这一点并且我真的想知道如何,因为这是让我困惑了一段时间......
[编辑]
为了澄清我的问题背后的动机,我想编写一个Exporter-class,让不同的,更专业的类派生出来.虽然顶层Exporter-class通常仅用于化妆品/结构目的,但派生了一个GraphExporter类,它应该再次作为基础类进行更加特殊的导出.但是,类似于Animal-example,我希望能够在专门的/派生类(例如,在SpecialGraphExplorer上)定义GraphExporter*,但是在调用"write(out_file)"时,它应该调用SpecialGraphExporter的相应成员函数GraphExporter :: write(out_file).
也许这使我的情况和意图更加清晰.
最好,
阴影
seh*_*ehe 31
经过一番思考后,我认为这是经典的多方法要求,即根据多个参数的运行时类型进行调度的方法.通常的虚拟功能是single dispatch
比较的(并且它们this
仅在类型上发送).
请参阅以下内容:
以下是维基百科文章中的"简单"方法供参考(对于大量派生类型,不太简单的方法可以更好地扩展):
// 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
Mik*_*son 13
显然,虚拟成员函数模板是不允许的,甚至在理论上也无法实现.要构建基类的虚拟表,需要有一定数量的虚函数指针条目.函数模板将允许无限量的"重载"(即实例化).
从理论上讲,语言(如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;
};
Run Code Online (Sandbox Code Playgroud)
以上是一个简洁的解决方案,因为它允许您在一个地方定义您想要的有限数量的重载(在Eater_impl类模板中),并且派生类中您需要的只是一个函数模板(可能还有额外的重载,特别案例).当然,还有一些开销,但我想可以多考虑一下来减少开销(额外的参考存储和Eater_impl的动态分配).我猜,奇怪的重复模板模式可能会以某种方式用于此目的.
Ind*_*ant 11
我认为访客模式可以是一个解决方案.
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;
}
Run Code Online (Sandbox Code Playgroud)
这打印:
I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
Run Code Online (Sandbox Code Playgroud)
根据 Mikael 的帖子,我做了另一个分支,使用 CRTP 并遵循 Eigenderived()
用于显式子类引用的风格:
// 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;
}
Run Code Online (Sandbox Code Playgroud)
输出:
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)
Run Code Online (Sandbox Code Playgroud)
这个片段可以在这里的源代码中找到:repro:c808ef0:cpp_quick/virtual_template.cc
不允许使用虚拟模板功能。不过,您可以在此处使用其中之一或另一个。
您可以使用虚拟方法创建一个界面,并通过饮食界面来实现各种动物。(即 PIMPL)
不太人类的直觉是将非成员非朋友模板函数作为自由函数,它可以对任何动物进行模板化 const 引用并让它们相应地吃东西。
根据记录,您不需要此处的模板。基类上的纯虚拟抽象方法足以强制和连接所有动物必须吃的地方,并定义它们如何通过覆盖来做到这一点,提供常规虚拟就足以说明所有动物都可以吃,但如果它们没有特定方式,然后他们可以使用此默认方式。