5

假设我有一堆水果:

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 语句的形式。

那么是否有一些我不知道的运行时魔法?或者我应该实现和维护一个充满动态转换的大麻烦 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 的一堆调用。

但是,正如其中一个答案所暗示的那样,可能还有另一个聪明的解决方案。如果 Fruits 使用 MustPeel() 和 MustWash() 之类的函数暴露出对食客很重要的品质会怎样?然后你可以用一个 Eat() 函数来解决......

更新: Daniel Newby 指出,使用 Visitor 也可以解决所提出的问题……但这需要一些语义倒立(Fruit::use 或 Fruit::beEaten?)。

虽然我想接受几个答案,但我认为 psmears 的答案实际上是未来读者的最佳答案。感谢大家。

4

5 回答 5

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)   { }

什么时候对水果什么都不做等同于吃水果?纯虚函数是更好的接口选择。

于 2010-06-15T22:12:19.987 回答
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_;
};
于 2010-06-15T23:43:11.057 回答
3

当出现这样的问题时,最好看看你为什么要做出特定的决定——例如,你为什么不想让水果类知道农药?

我确信这样做是有充分理由的——但表达这个理由将有助于在你的脑海中明确你的目标是什么——这通常会为构建程序的可能角度提供新的思路。

例如,您最终可能会添加新的虚拟方法“IsEdible”和“PrepareForEating”。然后,您可以为每种水果实现这些,并实现一个适用于所有水果的通用 Eat 方法 - 并且也摄取讨厌的杀虫剂 - 所有这些都不需要 Fruit 类对此一无所知。

当然,根据您的确切目标,这可能是完全不合适的 - 这就是为什么您必须在自己的脑海中澄清这个例子:-)

于 2010-06-15T22:20:28.407 回答
0

在标题中使用任意类指针没有任何问题。它们构成了许多习语的基础,如 PIMPL 和不透明指针。另外,如果你不是一个理智的人,你应该如何理解我的回答?

严重的是,存在派生函数和多态来解决这个问题。如果您拒绝使用语言提供的工具,为什么还要费心使用它呢?在任何情况下,您可以提出的任何解决方案都可以转换为虚函数调用,只是您需要手动编码而不是让编译器来完成。

于 2010-06-15T22:12:38.440 回答
0

你所要求的是不可能的。函数重载决议需要在编译时知道参数是哪个类,以便它可以调用正确的Eat函数。唯一的例外是您已经排除的虚拟成员函数。

于 2010-06-15T22:29:31.223 回答