web-dev-qa-db-ja.com

C ++の標準的な方法:仮想インターフェイスクラスとテンプレート

一般化とポリモーフィズムについて決定する必要があります。

シナリオは標準です。モノリシックな相互依存コードを、よりモジュール化され、クリーンで、拡張可能にしたいと考えています。設計原理の変更が可能な段階にあり、私が見ている限り、非常に望ましい段階です。

純粋に仮想の基本クラス(インターフェイス)またはテンプレートを導入しますか?

テンプレートオプションに関する基本事項を認識しています。間接参照の減少、パフォーマンスの向上、コンパイルの増加はありますが、遅延バインディングはありません。

Stlは多くの(またはまったく?)継承を使用せず、boostも使用しません。しかし、これらは、プログラマーが2行のコードごとに使用する非常に小さな基本的なツールを目指していると思います。

継承と遅延バインディングのアプローチは、デプロイ後または実行時にさえも交換可能、更新可能などである必要がある大きなコードと機能のプラグインスタイルにとってより賢明であると考えています。

さて、私のシナリオはやや中間にあります。

実行時にその場でコードを交換する必要はありません。コンパイル時は問題ありません。通常、これは非常に中心的で頻繁に使用される機能であり、論理的に大きなブロックに分離することはできません。

これにより、テンプレートソリューションにいくらか気を配ることができます。私にはそれもややきれいに見えます。

大きな悪い影響はありますか、インターフェースはまだ道のりですか?彼らはいつではないのですか?どちらが標準のC++スタイルに準拠していますか?

私はこれが主観的なものに接していることを知っていますが、私はいくつかの経験に本当に興味があります。私はScottMeyersの効果的なC++のコピーを持っていないので、皆さんに期待を寄せています:)

44
AndreasT

基本的には正しいです。実行時に型を変更できるようにする必要がある場合(プラグインアーキテクチャなど)、動的ポリモーフィズム(継承、仮想)が一般的に正しい選択です。型がコンパイル時にのみ変更される必要がある場合は、静的ポリモーフィズム(テンプレート)の方が適しています。

テンプレートの唯一の潜在的な欠点は、1)通常、ヘッダーで定義する必要があることです(つまり、より多くのコードが#includeされることを意味します)。これにより、コンパイル時間が遅くなることがよくあります。

しかし、デザイン的には、可能な場合はテンプレートの使用に問題は見られません。

どちらが標準のC++スタイルに準拠していますか?

「標準のC++スタイル」が何であるかによって異なります。 C++標準ライブラリは、すべてを少し使用します。 STLはすべてにテンプレートを使用し、少し古いIOStreamsライブラリは継承と仮想関数を使用し、Cから継承されたライブラリ関数はもちろんどちらも使用しません。

最近では、テンプレートが圧倒的に最も人気のある選択肢であり、それが最も「標準的な」アプローチであると言わざるを得ません。

29
jalf

古典的なオブジェクト指向ポリモーフィズムの特性:

  • オブジェクトは実行時にバインドされます。これはより柔軟性がありますが、実行時に多くのリソース(CPU)を消費します
  • 強い型付けは型の安全性をいくらか高めますが、dynamic_cast(そして顧客の顔に爆発する可能性)はそれを簡単に補うかもしれません
  • おそらくもっと広く知られ理解されていますが、「古典的な」深い継承階層は私には恐ろしいようです

テンプレートによるコンパイル時ポリモーフィズムのプロパティ:

  • コンパイル時のバインディングにより、より積極的な最適化が可能になりますが、実行時の柔軟性が妨げられます
  • ダックタイピングはもっと厄介に思えるかもしれませんが、失敗は通常コンパイル時の失敗です
  • 読みにくく、理解しにくい場合があります。概念がないと、コンパイラの診断が激怒することがあります

どちらかを決める必要はないことに注意してください。それらの両方(および他の多くのイディオムやパラダイム)を自由に組み合わせて混ぜることができます。多くの場合、これは非常に印象的な(そして表現力豊かな)コードにつながります。 (たとえば、型消去などを参照してください。)パラダイムを巧妙に組み合わせることで何が可能かを理解するには、Alexandrescuの「ModernC++ Design」を参照することをお勧めします。

11
sbi

それは偽りの反対のようなものです。はい、継承と仮想関数の主な用途はiostreamsにあります。これは非常に古く、他のstdライブラリとはまったく異なるスタイルで記述されています。

しかし、boostなどの「最もクールな」最新のC++ライブラリの多くは、ランタイムポリモーフィズムを利用しており、テンプレートを使用して使いやすくしています。

boost::anyおよびstd::tr1::function(以前はブーストからも)は良い例です。

これらは両方とも、コンパイル時に具体的なタイプが不明なものに対する単一アイテムのコンテナーです(これは、値を取得するための独自の種類の動的キャスト演算子があるため、anyで特に明白です)。

8

私のプレートでもう少し経験を積んだ後、テンプレートには気に入らないことがいくつかあります。テンプレートメタプログラミングを使用可能な言語として不適格にする特定の欠点があります。

  • 読みやすさ:括弧が多すぎる、言語が強制されていない(したがって誤用されている)規則が多すぎる
  • プログラミング言語で通常の進化を遂げている人にとって、テンプレートは判読できず、理解できません(boost bglを見てください)
  • 誰かがawkでc ++コードジェネレーターを書き込もうとしたように感じることがあります。
  • コンパイラのエラーメッセージはcl(utter)されたがらくたです
  • 機能のような基本的な「言語」を取得するには、必要なハックが多すぎます(それらのほとんどはc ++ 0xで修正されています)。
  • 実装ファイルにテンプレートがないため、ヘッダーのみのライブラリ(非常に両面の剣)になります
  • 通常のIDEのコード補完機能は、テンプレートではあまり役に立ちません。
  • MPLで大きなことをするのは「ぎこちない」ようで、そのための別の単語を見つけることができません。テンプレート化されたコードのすべての行は、そのテンプレートタイプに制約を生成します。これは、テキスト置換のような方法で適用されます。継承階層には固有のセマンティクスがあり、テンプレート構造には何もありません。これは、すべてがvoid *であり、コンパイラがセグメンテーション違反が発生するかどうかを通知しようとするようなものです。

そうは言っても、私は基本的なユーティリティやライブラリで非常にうまく使用しています。高レベルの機能やハードウェア関連のものをそれで書くことは、私にとってそれをカットするようには思えません。つまり、ビルディングブロックをテンプレート化しますが、家は古典的な方法で構築します。

5
AndreasT

私の見解では、それはあなたが得意とするものです。 OO OOを使用した経験がある場合。ジェネリックに関する経験が多い場合は、ジェネリックを使用してください。

どちらの手法にも、どちらかを使用できる多くのことを意味する同等のパターンがいくつかあります。 OO vsジェネリックスのポリシーまたはOO vsジェネリックスの一般的に繰り返されるテンプレートパターン)のテンプレートメソッドのEG戦略。

すでに機能しているが構造が少し臭いリファクタリングプロダクションコードを計画している場合。新しい設計手法を試す言い訳として使用しないでください。手法をよく理解してから1、2年後には、コードをリファクタリングした方法を後悔する可能性があります。あなたがそれらを学んでいるときに技術を適用するとき、新しい柔軟性の欠如を導入することは非常に簡単です。目的は、テクニックに熟練していない場合に既存のコードのデザインを改善することです。コードに大きな男根のシンボルを作成するのではなく、デザインを改善していることをどのように知っていますか。

個人的にはOOが得意で、理解しやすく、ほとんどの人が変更できるクリーンなデザインを作成できることを知っているので、それを好む傾向があります。私が正しい最も一般的なコードは、インターフェースを目的としています。他のジェネリックコード、例えばイテレータの記述、またはアルゴリズムのジェネリック関数。

0
iain

私は大きなコードベースで両方を使用しています。コンパイル時にタイプがわかっている場合はテンプレートを使用して設計し、実行時にのみわかっている場合は仮想関数を使用します。仮想関数はプログラミングが簡単で、後で読みやすいと思いますが、パフォーマンスが重要な場合があり、テンプレート化されたポリモーフィズム(実際にポリモーフィズムと呼べる場合)をインライン化できるという事実が非常に役立ちます。

0
miked