假设我有一堆水果:
class Fruit { ... };
class Apple : public Fruit { ... };
class Orange: public Fruit { ... };
对所述水果起作用的一些多态函数:
void Eat(Fruit* f, Pesticide* p) { ... }
void Eat(Apple* f, Pesticide* p) { ingest(f,p); }
void Eat(Orange* f, Pesticide* p) { peel(f,p); ingest(f,p); }
好的,等等。停在那儿。请注意,任何理智的人都会使Eat()成为Fruit类的虚拟成员函数。但这不是一种选择,因为我不是一个理智的人。另外,我不想在我的水果类的头文件中使用Pesticide *。
可悲的是,我希望接下来要做的就是成员函数和动态绑定允许的内容:
typedef list<Fruit*> Fruits;
Fruits fs;
...
for(Fruits::iterator i=fs.begin(), e=fs.end(); i!=e; ++i)
Eat(*i);
显然,这里的问题是我们传递给Eat()的指针将是Fruit *,而不是Apple *或Orange *,因此什么都不会被吃掉,我们都会非常饥饿。
所以我真的希望能够做到而不是这样:
Eat(*i);
就是这样:
Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i));
但是据我所知,这种魔法并不存在,除非可能是一个充满调用dynamic_cast的大讨厌if语句。
还有一些我不知道的运行时魔法吗?或者我应该实现并维护一个充满dynamic_casts的令人讨厌的if语句?或者我应该把它搞砸了,不要再考虑如何在Ruby中实现它,并允许一点Pesticide进入我的水果标题?
更新:而不是裸饮食功能和农药的设计,而是假设我只是不想把吃水果放在水果中,因为它毫无意义。知道怎么吃的水果?算了吧。相反,我需要一个带有Eat功能的Eater类,使用不同的代码来吃每种水果,以及一些默认代码,以防它是食者无法识别的水果:
class Eater
{
public:
void Eat(Apple* f) { wash(); nom(); }
void Eat(Orange* f) { peel(); nom(); }
void Eat(Fruit* f) { nibble(); }
};
...
Eater me;
for(Fruits::iterator i=fs.begin(), e=fs.end(); i!=e; ++i)
me.Eat(*i); //me tarzan! me eat!
但同样,这不起作用,C ++中的直接解决方案似乎是对dynamic_cast的一系列调用。
然而,正如其中一个答案所暗示的那样,可能还有另一个聪明的解决方案。如果Fruit通过MustPeel()和MustWash()等函数暴露出对食用者至关重要的品质,该怎么办?那么你可以使用一个Eat()函数......
更新: Daniel Newby指出使用Visitor也可以解决所提出的问题...但这需要一些语义倒立(Fruit :: use或Fruit :: beEaten?)。
虽然我想接受几个答案,但我认为psmears的答案实际上是未来读者最好的答案。谢谢,大家。
答案 0 :(得分:6)
你需要重新设计。也就是说,做你似乎想要避免的一切(出于什么原因,谁知道。)
多态行为需要多态函数。这意味着virtual
功能。 (或者你的dynamic_cast
的阶梯,这完全违背了目的......)
// fruit.h
class Pesticide; // you don't need a complete type
struct Fruit
{
virtual void Eat(Pesticide*) = 0;
};
// apple.h
class Apple : public Fruit
{
void Eat(Pesticide* p) { ... }
};
// orange.h
class Orange : public Fruit
{
void Eat(Pesticide* p) { ... }
};
如果您仍想要免费功能*:
void Eat(Fruit* f, Pesticide* p) { f->Eat(p); }
*请注意,您的帖子已经表明设计不佳;即第一个Eat
函数:
void Eat(Fruit* f, Pesticide* p) { }
什么时候对水果做什么都等于吃水果?纯虚函数是一个更好的界面选择。
答案 1 :(得分:3)
当出现这样的问题时,最好仔细查看为什么要做出特定的决定 - 例如,为什么你不想要Fruit类了解农药?
我确信 是一个很好的理由 - 但是表达这个理由将有助于在你的脑海中准确地阐明你的目标是什么 - 这通常会为可能的角度提供一个新的视角。构建程序。
例如,您最终可能会添加新的虚拟方法“IsEdible”和“PrepareForEating”。然后你可以为每个水果实施这些,并实施一种适用于所有水果的通用Eat方法 - 并且摄取麻烦的农药 - 所有这些都没有水果类知道它的任何内容。
当然,根据您的确切目标,这可能是完全不合适的 - 这就是为什么您必须在自己的脑海中澄清这个例子: - )
答案 2 :(得分:3)
只需使用我就在这里!图案。它就像访客模式,但没有容器。
// fruit.h
class Fruit;
class Apple;
class Orange;
class Fruit_user {
public:
Fruit_user();
virtual ~Fruit_user();
virtual use(Apple *f) = 0;
virtual use(Orange *f) = 0;
};
class Fruit {
public:
// Somebody with strong template fu could probably do
// it all here.
virtual void use(Fruit_user *fu) = 0;
};
class Apple : public Fruit {
public:
virtual void use(Fruit_user *fu) {
fu->use(this);
}
};
class Orange: public Fruit {
public:
virtual void use(Fruit_user *fu) {
fu->use(this);
}
};
// dow-chemical.h
class Pesticide_fruit_user : public Fruit_user {
public:
Pesticide_fruit_user(Pesticide *p) {
p_ = p;
}
virtual void use(Apple *f) { ingest(f, p_); }
virtual void use(Orange *f) { peel(f, p_); ingest(f, p_); }
private:
Pesticide *p_;
};
答案 3 :(得分:0)
在标题中使用任意类指针没有任何问题。它们构成了许多习语的基础,如PIMPL和不透明指针。另外,如果你不是一个理智的人,你怎么理解我的答案呢?
严重的是,派生函数和多态存在解决这个问题。如果您拒绝使用语言提供的工具,为什么还要费心呢?在任何情况下,您可以提出的任何解决方案都可以转换为虚函数调用,只需要手动编写它而不是让编译器执行它。
答案 4 :(得分:0)
你要求的是不可能的。函数重载分辨率需要在编译时知道 参数是哪个类,因此它可以调用正确的Eat
函数。唯一的例外是虚拟成员函数,您已经排除了这些函数。