苹果、橙子和指向最衍生的 c++ 的指针;班级

发布于 2024-09-05 15:45:53 字数 1791 浏览 3 评论 0原文

假设我有一堆水果:

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));

但据我有限的知识,这样的魔法并不存在,除非可能以一个充满动态_cast 调用的大讨厌的 if 语句的形式存在。

那么是否存在一些我不知道的运行时魔法?或者我应该实现并维护一个充满动态强制转换的大而令人讨厌的 if 语句?或者我应该接受它,停止思考如何在 Ruby 中实现这一点,并允许一点农药进入我的水果头?

更新: 假设我只是不想将 Eat 放入水果中,因为它没有意义,而不是使用简单的 Eat 函数和杀虫剂。一种知道如何吃自己的水果?噗。相反,我需要一个具有 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 也可以解决所提出的问题...但这需要一点语义倒立(水果::使用还是水果::被吃掉?)。

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

Suppose I have a bunch of fruit:

class Fruit { ... };
class Apple : public Fruit { ... };
class Orange: public Fruit { ... };

And some polymorphic functions that operate on said 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); }

OK, wait. Stop right there. Note at this point that any sane person would make Eat() a virtual member function of the Fruit classes. But that's not an option, because I am not a sane person. Also, I don't want that Pesticide* in the header file for my fruit class.

Sadly, what I want to be able to do next is exactly what member functions and dynamic binding allow:

typedef list<Fruit*> Fruits;
Fruits fs;
...
for(Fruits::iterator i=fs.begin(), e=fs.end(); i!=e; ++i)
    Eat(*i);

And obviously, the problem here is that the pointer we pass to Eat() will be a Fruit*, not an Apple* or an Orange*, therefore nothing will get eaten and we will all be very hungry.

So what I really want to be able to do instead of this:

Eat(*i);

is this:

Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i));

But to my limited knowledge, such magic does not exist, except possibly in the form of a big nasty if-statement full of calls to dynamic_cast.

So is there some run-time magic of which I am not aware? Or should I implement and maintain a big nasty if-statement full of dynamic_casts? Or should I suck it up, quit thinking about how I would implement this in Ruby, and allow a little Pesticide to make its way into my fruit header?

Update: Instead of the contrived bit with the bare Eat functions and Pesticide, suppose instead that I just don't want to put Eat in the fruit because it makes no sense. A fruit that knows how to eat itself? Pshaw. Instead I need an Eater class with an Eat function, with different code for eating each kind of fruit, and some default code in case it's a fruit that the eater doesn't recognize:

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!

But again, this doesn't work, and the straightforward solution in C++ seems to be a bunch of calls to dynamic_cast.

However, as one of the answers suggests, there may be another clever solution. What if Fruits exposed the qualities that mattered to eaters, with functions like MustPeel() and MustWash()? Then you could get by with a single Eat() function ...

Update: Daniel Newby points out that using Visitor also solves the problem as presented ... but this requires a bit of a semantic headstand (Fruit::use or Fruit::beEaten?).

While I'd like to accept several answers, I think psmears's answer is actually the best one for future readers. Thanks, everyone.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

有深☉意 2024-09-12 15:45:53

你需要重新设计。也就是说,做你似乎在避免的一切(出于什么原因,谁知道)。

多态行为需要多态函数。这意味着虚拟函数。 (或者你的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)   { }

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

You need to redesign. Namely, do everything you seem to be avoiding (for what reason, who knows.)

Polymorphic behavior requires polymorphic functions. This means a virtual function. (Or your ladder of dynamic_cast's, which completely defeats the purpose...)

// 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) { ... }
};

If you still want a free function*:

void Eat(Fruit* f, Pesticide* p)   { f->Eat(p); }

*Note that your post is already indicative of bad design; namely the first Eat function:

void Eat(Fruit* f, Pesticide* p)   { }

When does doing nothing to a fruit equate to eating the fruit? A pure virtual function is a much better interface choice.

染柒℉ 2024-09-12 15:45:53

当出现这样的问题时,最好准确地了解为什么您想要做出特定的决定 - 例如,为什么您不想让 Fruit 类知道农药?

我确信这样做有一个很好的理由 - 但表达这个理由将有助于在你的头脑中明确你的目标是什么 - 这通常会为构建计划的可能角度提供新的启示。

例如,您最终可能会添加新的虚拟方法“IsEdible”和“PrepareForEating”。然后,您可以为每种水果实现这些,并实现一种适用于所有水果的通用 Eat 方法 - 并且也摄入讨厌的农药 - 所有这些 Fruit 类都不知道它。

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

When a question like this comes up, it's good to look at exactly why you want to make particular decisions - for instance, why do you not want the Fruit classes to know about Pesticide?

I'm sure there is a good reason for this - but expressing that reason will help clarify in your mind exactly what your aims are - and this often sheds a new light on a possible angle for structuring the program.

For instance, you might end up adding new virtual methods "IsEdible" and "PrepareForEating". Then you can implement these for each fruit, and implement one generic Eat method that works for all fruits - and ingests the pesky pesticide too - all without the Fruit classes knowing anything about it.

Of course, depending on your precise aims, that may be totally inappropriate - which is why you'll have to clarify the example in your own head :-)

魂牵梦绕锁你心扉 2024-09-12 15:45:53

只需使用“我就站在这里!”图案。它类似于访客模式,但没有容器。

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

Just use the I Am Standing Right Here! Pattern. It's like the Visitor Pattern but without a container.

// 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_;
};
度的依靠╰つ 2024-09-12 15:45:53

在标头中包含任意类指针没有任何问题。它们构成了许多惯用语的基础,例如 PIMPL 和不透明指针。另外,如果你不是一个理智的人,你怎么能理解我的回答呢?

说真的,派生函数和多态性的存在就是为了解决这个问题。如果您拒绝使用语言提供的工具,为什么还要使用它呢?在任何情况下,您能想到的任何解决方案都可以转换为虚拟函数调用,只是您需要手动编码而不是让编译器执行它。

There's nothing wrong with having arbitrary class pointers in headers. They form the basis of many idioms, like PIMPL and opaque pointers. Also, if you aren't a sane person, how are you supposed to understand my answer?

Seriously, derived functions and polymorphism exist to solve this problem. If you refuse to use the language provided tools, why bother using it at all? Any solution you can come up with can be translated into a virtual function call in any case, just you would have coded it manually instead of having the compiler do it.

平安喜乐 2024-09-12 15:45:53

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

What you're asking for isn't possible. The function overloading resolution needs to know at compile time which class the parameter is so it can call the correct Eat function. The only exception is for virtual member functions, which you've already ruled out.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文