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

OK、待ってください。すぐそこに止まります。この時点で、正気の人なら誰でもEat()をFruitクラスの仮想メンバー関数にすることに注意してください。しかし、私は正気の人ではないので、それは選択肢ではありません。また、フルーツクラスのヘッダーファイルにその農薬*を含めたくありません。

悲しいことに、次にできることは、メンバー関数と動的バインディングで可能なことです。

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

そして明らかに、ここでの問題は、Eat()に渡すポインターがApple*やOrange*ではなくFruit*になるため、何も食べられず、私たち全員が非常に空腹になることです。

だから私がこれの代わりに本当にやりたいこと:

Eat(*i);

これは:

Eat(MAGIC_CAST_TO_MOST_DERIVED_CLASS(*i));

しかし、私の限られた知識では、そのような魔法は存在しません。ただし、dynamic_castへの呼び出しでいっぱいの大きな厄介なifステートメントの形式を除いては。

それで、私が気付いていない実行時の魔法はありますか?または、dynamic_castsでいっぱいの大きな厄介なifステートメントを実装して維持する必要がありますか?それとも、私はそれを吸い上げて、Rubyでこれをどのように実装するかについて考えるのをやめ、少量の農薬が私の果物のヘッダーに入るのを許可する必要がありますか?

更新:裸のEat関数とPesticideを使った工夫の代わりに、意味がないのでEatを果物に入れたくないと仮定します。自分で食べる方法を知っている果物?Pshaw。代わりに、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への一連の呼び出しのようです。

ただし、回答の1つが示唆しているように、別の賢い解決策があるかもしれません。Fruitsが、MustPeel()やMustWash()などの関数を使用して、食べる人にとって重要な品質を公開した場合はどうなりますか?次に、単一のEat()関数でうまくいくことができます...

更新: Daniel Newbyは、Visitorを使用すると、提示された問題も解決されると指摘しています...しかし、これにはセマンティックな逆立ちが必要です(Fruit::useまたはFruit::beEaten?)。

私はいくつかの答えを受け入れたいと思いますが、psmearsの答えは実際には将来の読者にとって最良のものだと思います。みんな、ありがとう。

4

5 に答える 5

6

再設計する必要があります。つまり、回避していると思われるすべてのことを実行します(どのような理由で、誰が知っているか)。

ポリモーフィックな振る舞いには、ポリモーフィックな関数が必要です。これはvirtual関数を意味します。dynamic_cast(または、目的を完全に無効にする'sのはしご...)

// 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

このような質問が出てきたら、なぜ特定の決定をしたいのかを正確に調べるのは良いことです。たとえば、フルーツのクラスに農薬について知られたくないのはなぜですか。

これには正当な理由があると確信していますが、その理由を表現することは、あなたの目的が正確に何であるかを頭の中で明確にするのに役立ちます。これは、プログラムを構築するための可能な角度に新しい光を当てることがよくあります。

たとえば、新しい仮想メソッド「IsEdible」と「PrepareForEating」を追加することになります。次に、これらを果物ごとに実装し、すべての果物に対して機能する1つの一般的なEatメソッドを実装できます。また、厄介な農薬も摂取します。すべて、Fruitクラスはそれについて何も知りません。

もちろん、あなたの正確な目的によっては、それは完全に不適切かもしれません-それがあなたがあなた自身の頭の中で例を明確にしなければならない理由です:-)

于 2010-06-15T22:20:28.407 に答える
3

I Am Standing Right Hereを使用してください!パターン。ビジターパターンに似ていますが、コンテナーがありません。

// 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 に答える
0

ヘッダーに任意のクラスポインタを含めることには何の問題もありません。これらは、PIMPLや不透明なポインターなどの多くのイディオムの基礎を形成します。また、あなたが正気でないなら、私の答えをどのように理解することになっていますか?

真剣に、この問題を解決するために派生関数と多形が存在します。言語が提供するツールの使用を拒否した場合、なぜわざわざそれを使用するのでしょうか。思いついたソリューションは、どのような場合でも仮想関数呼び出しに変換できます。コンパイラに実行させるのではなく、手動でコーディングするだけです。

于 2010-06-15T22:12:38.440 に答える
0

あなたが求めていることは不可能です。関数のオーバーロード解決は、正しい関数を呼び出すことができるように、コンパイル時にパラメーターがどのクラスであるかを知る必要があります。Eat唯一の例外は、すでに除外している仮想メンバー関数の場合です。

于 2010-06-15T22:29:31.223 に答える