5

(この質問は、おそらく Stroustrup を参照して回答する必要があります。)

次のように、最も派生したクラスへのポインターを要求できると非常に便利です。

class Base { ... };
class DerivedA { ... };
class DerivedB { ... };
class Processor
{
  public:
  void Do(Base* b) {...}
  void Do(DerivedA* d) {...}
  void Do(DerivedB* d) {...}
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    p.Do(CAST_TO_MOST_DERIVED_CLASS(*i));
}

ただし、このメカニズムは c++ では提供されていません。なんで?

更新、動機付けの例:

Base と Derived と Processor の代わりに、次のものがあるとします。

class Fruit
class Apple : public Fruit
class Orange: public Fruit

class Eater
{
   void Eat(Fruit* f)  { ... }
   void Eat(Apple* f)  { Wash(f); ... }
   void Eat(Orange* f) { Peel(f); ... }
};

Eater me;
for each Fruit* f in Fruits
    me.Eat(f);

しかし、これを C++ で行うのは難しく、ビジター パターンのような独創的なソリューションが必要です。問題は、「CAST_TO_MOST_DERIVED」のようなものを使用するとはるかに簡単になるのに、C++ でこれを行うのが難しいのはなぜですか?

更新: ウィキペディアはすべてを知っている

ポンタス・ガッジは良い答えを持っていると思います。ウィキペディアのMultiple Dispatchのエントリから、次のビットを追加します。

「Stroustrup は、The Design and Evolution of C++のマルチメソッドの概念が好きで、C++ での実装を検討したと述べていますが、(仮想関数に匹敵する) 効率的なサンプル実装を見つけることができず、型のあいまいさの問題を解決できなかったと主張しています。 . 彼は続けて、この機能はまだあればいいのですが、上記の C/C++ の例で概説したように、ダブル ディスパッチまたは型ベースのルックアップ テーブルを使用してほぼ実装できるため、将来の言語では優先度の低い機能であると述べています。リビジョン。」

背景については、 Multi-Methodsについての簡単な要約を読むことができます。これは、私が言及したような呼び出しよりも優れているでしょう。

4

10 に答える 10

8

おそらく、それが代わりに仮想関数が行うことだからです。最も派生したクラスに最も近い仮想関数の実装は、基本クラスのポインターまたは参照を介して呼び出すと呼び出されます。

于 2010-06-16T14:19:17.397 に答える
8

まず、C++ では、最も派生したクラスへのポインターを数値で (つまり、アドレスの数値だけで) 要求できます。これがやるべきdynamic_castことvoid*です。

第二に、最も派生したクラスの正確な型の範囲内で、最も派生したクラスへのポインターを取得する方法がありません。C++ では、キャストは静的型で機能し、静的型はコンパイル時の概念です。型ベースの関数のオーバーロードもコンパイル時のプロセスです。あなたのケースでは、コンパイル時に正確な最も派生した型がわからないため、キャストできず、オーバーロードを解決できません。このようなキャストを要求しても、C++ 言語の領域では意味がありません。

あなたが実装しようとしているもの (私があなたの意図を正しく理解していれば) は、キャストではなく、まったく異なる方法で実装されています。一例として、double dispatchについて読んでください。

于 2010-06-16T14:36:30.787 に答える
6

i の型はコンパイル時に決定できないためです。したがって、コンパイラは生成する関数呼び出しを認識できません。C++ は、仮想関数メカニズムである動的ディスパッチの 1 つのメソッドのみをサポートします。

于 2010-06-16T14:22:16.957 に答える
4

仮想関数呼び出しを使用して呼び出されます。プロセッサー* を DerivedA/B の仮想メソッドに渡します。その逆ではありません。

まったく不要で冗長であるため、メカニズムは提供されていません。

誓って、私はこの正確な質問を 1 日か 2 日前に提出しました。

于 2010-06-16T14:17:33.627 に答える
4

あなたが提案していることはswitch、オーバーロードされた関数の1つを呼び出すランタイム型の a と同等です。他の人が示したように、継承階層に反対するのではなく、継承階層で作業する必要があります。クラス階層の外側にディスパッチするのではなく、クラス階層で仮想を使用します。

そうは言っても、このようなものは、特に の階層もある場合に、二重ディスパッチProcessorsに役立つ可能性があります。しかし、コンパイラはそれをどのように実装するのでしょうか?

まず、実行時に「最もオーバーロードされた型」と呼ばれるものを抽出する必要があります。それは可能ですが、多重継承やテンプレートなどをどのように処理しますか? 言語のすべての機能は、他の機能と適切に相互作用する必要があります。C++ には多数の機能があります。

次に、コード例を機能させるには、ランタイム タイプに基づいて正しい静的オーバーロードを取得する必要があります (C++ では設計上許可されていません)。特に複数のパラメーターを使用して、コンパイル時のルックアップ規則に従うことを希望しますか? このランタイム ディスパッチで、Processor階層のランタイム タイプと、追加されたオーバーロードも考慮しますか? コンパイラがランタイム ディスパッチャーに自動的に追加するロジックの量はどれくらいですか? 無効なランタイム タイプをどのように処理しますか? この機能のユーザーは、単純なキャストと関数呼び出しのコストと複雑さを認識していますか?

全体として、この機能は実装が複雑で、実装と使用の両方でエラーが発生しやすく、まれにしか役に立たないと思います。

于 2010-06-16T14:46:18.437 に答える
2

あなたはダブルディスパッチを探しています。そのリンクに示されているように、C++ で実行できますが、見栄えがよくなく、基本的には、相互に呼び出す 2 つの仮想関数を使用する必要があります。継承ツリー内の一部のオブジェクトを変更できない場合、この手法も使用できない可能性があります。

于 2010-06-16T14:50:31.230 に答える
2

C++ では、オーバーロードの解決はコンパイル時に行われます。あなたの例では、実行時に実際の型を決定する必要があり*iます。実行時にこれを行うには、実行時の型チェックが必要になります。C++ はパフォーマンス指向の言語であるため、意図的にこのコストを回避します。もしあなたが本当にこれをやりたいのであれば (もっと現実的な例を見たいと思っています)、最も派生したクラスに dynamic_cast できます。クラス階層を前もって。また、完全な階層を事前に把握することはおそらく不可能です。DerivedB クラスがパブリック ヘッダーにある場合、別のライブラリがそれを使用して、さらに派生したクラスを作成している可能性があります。

于 2010-06-16T14:48:17.347 に答える
1

なぜC++にはそれがないのですか? 制作側は考えたことがなかったのかもしれません。あるいは、彼らはそれが適切または有用であると十分に考えていなかったのかもしれません。あるいは、実際にこの言語でやろうとすると問題があったのかもしれません。

その最後の可能性について、ここに思考実験があります:

this 機能が存在すると、指定された動的型を調べて適切なオーバーロードを呼び出すコードをコンパイラが記述できるようになります。ここで、コードの別の部分にclass DerivedC : Base {...};. Processor::Doそして、対応するオーバーロードが追加されていないと言います。

以上のことから、プログラムが適切なオーバーロードを選択しようとするとき、プログラムは何をすべきでしょうか? この不一致は、コンパイル時にキャッチできません。基本クラスに一致する関数を見つけるために、クラス階層を登ろうとする必要がありますか? 特別な例外をスローする必要がありますか? クラッシュするだけですか?他の可能性はありますか?コードとクラス階層の意図を知らなくても、コンパイラが独自に行うことができる合理的な選択は実際にありますか?

はい、そのような機能を自分で作成すると、同じ問題が発生する可能性がありますが、コンパイラではなく、プログラマが動作を選択する完全な制御を持っています。

于 2010-06-16T23:36:16.137 に答える
1

これは C++ では不可能ですが、達成したいことはVisitor デザイン パターンを使用して簡単に実行できます。

class Base
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedA
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class DerivedB
{
    virtual void accept(BaseVisitor& visitor) { visitor.visit(this); }
};

class BaseVisitor
{   
    virtual void visit(Base* b) = 0;
    virtual void visit(DerivedA* d) = 0;
    virtual void visit(DerivedB* d) = 0;
};

class Processor : public BaseVisitor
{
    virtual void visit(Base* b) { ... }
    virtual void visit(DerivedA* d) { ... }
    virtual void visit(DerivedB* d) { ... }
};

list<Base*> things;
Processor p;
for(list<Base*>::iterator i=things.begin(), e=things.end(); i!=e; ++i)
{
    (*i)->visit(p);
}
于 2010-06-16T14:57:37.677 に答える
0

C++ は、関連付けられた型のコンテキストでデータを解釈します。DerivedA* または DerivedB* のインスタンスをリストに格納する場合、その関連型は必ず Base* でなければなりません。これは、コンパイラ自体が、それらが基本クラスではなくサブクラスの 1 つへのポインターであることを判断できなくなったことを意味します。理論的には、関連付けられた型の継承を調べることで LESS 派生クラスにキャストできますが、必要なことを実行するために必要な情報は、コンパイル時には利用できません。

于 2010-06-16T14:30:56.180 に答える