8

コアとなるダイナミック バインディングとテンプレートは根本的に異なるものですが、同じ機能を実装するために使用できます。

コード例 (参考用)

A) 動的バインディング

namespace DB {
  // interface
  class CustomCode {
    public:
      virtual void operator()(char) const = 0;
  };
  class Lib {
    public:
      void feature(CustomCode const& c) {
        c('d');
      }
  };

  // user code
  class MyCode1 : public CustomCode {
    public:
      void operator()(char i) const {
        std::cout << "1: " << i << std::endl;
      }
  };
  class MyCode2 : public CustomCode {
    public:
      void operator()(char i) const {
        std::cout << "2: " << i << std::endl;
      }
  };

  void use() {
    Lib lib;
    lib.feature(MyCode1());
    lib.feature(MyCode2());
  }
}

B) 汎用プログラミング

namespace GP {
  //interface
  template <typename CustomCode> class Lib {
    public:
      void feature(CustomCode const& c) {
        c('g');
      }
  };

  // user code
  class MyCode1 {
    public:
      void operator()(char i) const {
        std::cout << "1: " << i << std::endl;
      }
  };
  class MyCode2 {
    public:
      void operator()(char i) const {
        std::cout << "2: " << i << std::endl;
      }
  };

  void use() {
    Lib<MyCode1> lib;
    lib.feature(MyCode1());
    //lib.feature(MyCode2());  <-- illegal
  }
}

質問

いくつかの考え

これらのパラダイムは同一ではなく、長所と短所Aがあります ( の方が強力 ( を参照MyCode2) でありB、ユーザーにとってより柔軟です) が、両方とも同じ機能を実装できます (上記の制限が適用されます)。

とにかく、理論的には、 (TM)Aは仮想関数の間接化のために実行時に少し遅くなりますが、Bメソッドをインライン化できるため、いくつかの最適化の機会が提供されます (もちろん、間接化はありません)。ただし、実装する必要がある明確なインターフェイス (通常は複数のメソッドで構成されます) があるため、もう少し自己文書化されていると
感じることがよくありますが、もう少し無政府的です (柔軟性を意味します)。AB

  1. これらのパラダイムの一般的な結果/比較研究はありますか?
  2. スピードアップは重要ですか?
  3. コンパイル時間はどうですか?
  4. 大規模システムのインターフェースに対する設計上の意味は何ですか (私は主にAモジュール間インターフェースに使用しましたが、これまで本当に大きなプロジェクトを行ったことはありません)。

編集

注: 「より強力なので動的バインディングの方が優れている」と言うのは、まったく答えではありません。前提条件は、両方のアプローチが適用できる場合があるためです (そうでない場合、選択する自由はありません。少なくとも合理的ではありません)。 .

4

2 に答える 2

6

これらのパラダイムの一般的な結果/比較研究はありますか?

私が見た限りでは、証明の多くの例が記事や出版物に見られます。お気に入りの C++ の本には、いくつかのデモが含まれているはずです。そのようなリソースがない場合は、Modern C++ Design: Generic Programming and Design Patterns Applied - A. Alexandrescuを読むことをお勧めします。ただし、あなたの質問に直接答えることができる特定のリソースはありません。同様に、結果は実装とコンパイラによって異なります。コンパイラの設定でさえ、そのようなテストの結果に大きく影響する可能性があります。(これは、この特定の質問に対する回答とはみなされませんが、各質問への回答です)。

スピードアップは重要ですか?

短い答え:場合によります。

あなたの例では、コンパイラは実際に静的ディスパッチを使用するか、仮想関数呼び出しをインライン化することさえできます(コンパイラには十分な情報が表示されます)。私は今、応答を些細な例 (具体的には OP) から離れて、より大きく、より複雑なプログラムに移動しようとしています。

「場合による」の拡張: はい、速度の向上は測定不能なものから巨大なものまでさまざまです。ジェネリックを介してコンパイル時にコンパイラに信じられないほどの量の情報が提供される可能性があることを認識しておく必要があります (おそらく既に)。この情報を使用して、プログラムをより正確に最適化できます。この良い例の 1 つは、std::arrayvsの使用ですstd::vector。ベクトルは実行時の柔軟性を高めますが、コストは非常に大きくなる可能性があります。ベクトルはサイズ変更のためにさらに実装する必要があり、動的割り当ての必要性はコストがかかる可能性があります。他にも違いがあります。配列のバッキング割り当ては変更されず (++最適化)、要素数は固定されます (++最適化)。また、多くの場合、new を介して呼び出す必要はありません。

この例は元の質問から大きく逸脱していると思われるかもしれません。多くの点で、実際にはそれほど違いはありません。コンパイラは、複雑さが増すにつれて、プログラムについてより多くのことを知っています。この情報は、プログラム (デッド コード) のいくつかの部分を削除することができstd::array、例として使用すると、型が提供する情報は、コンパイラが簡単に「ああ、この配列のサイズは 7 要素であることがわかりました。それに応じてループする」と、命令が少なくなり、予測ミスがなくなります。他にもたくさんありますが、配列/ベクトルの場合、最適化されたプログラムの実行可能サイズが から次vectorのようなインターフェイスに変換すると 20% に減少するのを見てきました。array. また、コードは数倍速く実行できます。実際、一部の式はコンパイル時に完全に計算できます。

動的ディスパッチにはまだ利点があり、動的ディスパッチを正しく使用すれば、プログラムの速度も向上します。実際に学ぶ必要があるのは、どちらを優先するかを決定することです。多くの変数を持つ巨大な関数を非常に効果的に最適化できないのと同様に (実際のプログラムでテンプレートを展開した結果)、仮想関数呼び出しは実際には多くの状況でより高速でクリーンなアプローチになる可能性があります。そのため、これらは 2 つの別個の機能であるため、何が正しいかを判断するにはある程度の練習が必要です (多くのプログラマーは、これを十分に習得するのに時間をかけません)。

結論として、それらは異なるシナリオに適用可能な個別の機能と見なされるべきです。これらは(imho)、現実の世界で実際に行われているよりも実際的な重複がはるかに少ないはずです。

コンパイル時間はどうですか?

テンプレートを使用すると、開発中のコンパイルとリンクの時間が非常に長くなる可能性があります。ヘッダー/テンプレートが変更されるたびに、すべての依存関係をコンパイルする必要があります。これは、多くの場合、動的ディスパッチを優先するための大きな恩恵となります。もちろん、事前に計画を立てて適切に構築すれば、これを減らすことができますテンプレートを使用して習得するのははるかに難しいテーマです。テンプレートを使用すると、大規模なビルドの頻度が増えるだけでなく、大規模なビルドの時間と複​​雑さが増すことがよくあります。(さらに注意事項が続きます)

大規模なシステムのインターフェースに対する設計上の意味は何ですか (私は主にモジュール間インターフェースに A を使用しましたが、これまで本当に大きなプロジェクトを行ったことはありません)。

それは本当にあなたのプログラムの期待に依存します。私はvirtual毎年書くことを減らしています(そして他の多くの人も)。他のアプローチの中でも、テンプレートはますます一般的になっています。正直なところ、B「アナキズム」がどの程度なのか理解できません。私にとってAは、適切な代替手段がたくさんあるため、少し時代遅れです。最終的には、大規模なシステムを適切に設計するために多くの考慮事項が必要となる設計上の選択です。優れたシステムは、言語の機能を健全に組み合わせて使用​​します。歴史は、重要なプログラムを書くためにこの議論の機能が必要でないことを証明していますが、誰かがいくつかの特定の用途でより良い代替案を見つけたので、すべての機能が追加されました.

一般化:

  • テンプレートを正しく使用すると、実行が大幅に高速化されます。
  • どちらもより大きな実行可能ファイルを生成できます。正しく使用され、実行可能サイズが重要である場合、ライターは複数のアプローチを使用して実行可能サイズを縮小し、使いやすいインターフェイスを提供します。
  • テンプレートは非常に複雑になる可能性があります。エラーメッセージをクロールして解釈することを学ぶには時間がかかります。
  • テンプレートは、いくつかのエラーをコンパイル ドメインにプッシュします。個人的には、実行時エラーよりもコンパイル エラーを好みます。
  • 多くの場合、仮想を介してコンパイル時間を短縮するのは簡単です (仮想は .cpp に属します)。プログラムが巨大な場合、頻繁に変更される巨大なテンプレート システムは、モジュール間の可視性と依存関係が多く存在するため、再構築時間とカウントを屋根からすばやく送信する可能性があります。
  • コンパイル時間を短縮するために、コンパイル済みファイルの数が少ない遅延および/または選択的インスタンス化を使用できます。
  • 大規模なシステムでは、チーム/クライアントに自明ではない再コンパイルを強制することについて、より配慮する必要があります。仮想を使用することは、これを最小限に抑えるための 1 つのアプローチです。同様に、メソッドのより高い割合が cpp で定義されます。もちろん、別の方法として、実装の多くを非表示にするか、インターフェースをより表現力豊かに使用する方法をクライアントに提供することもできます。
  • 大規模なシステムでは、テンプレート、ラムダ/ファンクターなどを実際に使用して、結合と依存関係を大幅に削減できます。
  • バーチャルは依存性を高め、多くの場合、保守が困難になり、インターフェイスが膨張し、構造的に扱いにくい獣になります。テンプレート中心のライブラリは、その優先順位を逆転させる傾向があります。
  • すべてのアプローチが間違った理由で使用される可能性があります。

結論 大規模で適切に設計された最新のシステムは、多くのパラダイムを効果的かつ同時に使用します。現在、ほとんどの場合仮想を使用している場合は、(imo) 間違っていることになります。特に、C++ 11 を吸収する時間があった後でも、それがまだアプローチである場合はそうです。速度、パフォーマンス、および/または並列処理も重大な懸念事項である場合、テンプレートとラムダは親しい友人になるに値します。

于 2011-09-19T08:00:12.257 に答える
1

どちらが良いですか?場合によります。あなたはオーバーラップに集中しました。アプローチが分岐する場所に焦点を当てることをお勧めします。また、両方のアプローチを同時に使用する必要がある場所も見逃していました。

テンプレートの最大の利点は、型にはまったコードを、場合によっては大幅に削減できることです。テンプレートのもう 1 つの利点は、メタプログラミングです。SFINAE のおかげでできる本当に奇妙なことがいくつかあります。

テンプレートの欠点の 1 つは、構文が少しぎこちないことです。これを回避する方法はありません。それが現実さ。テンプレートのもう 1 つの欠点は、各インスタンス化が異なるクラスであり、同じテンプレート クラスからインスタンス化された他のクラスとはまったく関係がないことです。これを回避する方法があります: 両方のアプローチを組み合わせます。テンプレート クラスをテンプレート以外の基本クラスから派生させます。もちろん、実行時の利点の一部を失ってしまいました。

ポリモーフィズムの最大の利点は、動的であることです。これは非常に大きな勝利になる可能性があります。それを割引しないでください。このようなポリモーフィズムにはパフォーマンスのペナルティがありますが、共通のインターフェースに従うオブジェクトのコレクションを持ちたいが、異なるオブジェクトがそのインターフェースに対して異なる実装を持っている場合、何らかの方法でそのペナルティを支払うことになります。

于 2011-09-18T23:30:48.997 に答える