8

依存性注入を使用する C++ の方法は何ですか? テンプレートまたはポリモーフィック クラスを使用していますか? 次のコードを検討してください。

class AbstractReader
{
public:
  virtual void Read() = 0;
};

class XMLReader : public AbstractReader
{
public:
  void Read()  { std::cout << "Reading with a XML reader" << std::endl; }
};

class TextFileReader : public AbstractReader
{
public:
  void Read()  { std::cout << "Reading with a Text file reader" << std::endl; }
};

class Parser
{
public:
  Parser(AbstractReader* p_reader) : reader(p_reader) { }
  void StartParsing() { reader->Read();
    // other parsing logic
  }
private:
  AbstractReader* reader;
};

template<class T>
class GenericParser
{
public:
  GenericParser(T* p_reader) : reader(p_reader) { }

  void StartParsing()
  {
    reader->Read();
  }
private:
  T* reader;
};

1 - 最適な方法はどれですか? GenericParserまたはパーサー? GenericParserであれば、継承を削除できることがわかっています。

2 - テンプレートを使用する場合、すべてのコードをヘッダー ファイルに記述しても問題ありませんか? テンプレートを使用する多くのクラスが、.h/.cpp の組み合わせではなく、すべてのコードをヘッダー ファイルに書き込むのを見てきました。インライン化など、そうすることに何か問題はありますか?

何かご意見は?

4

4 に答える 4

15

コードまたはヘッダーファイルをどのように構造化するかに基づいて、これを自由に選択することはできません。答えは、アプリケーションの要件によって決まります。

結合をコンパイル時に決定できるか、実行時まで遅らせる必要があるかによって異なります

コンポーネントとその依存関係の間の結合がコンパイル時に永続的に決定される場合は、テンプレートを使用できます。これで、コンパイラーはインライン化を実行できるようになります。

ただし、実行時に結合を決定する必要がある場合(たとえば、ユーザーが依存関係を提供する他のコンポーネントを選択する場合、おそらく構成ファイルを介して)、そのためのテンプレートを使用することはできず、実行時のポリモーフィックメカニズムを使用する必要があります。その場合、選択肢には仮想関数、関数ポインター、またはが含まれますstd::function

于 2009-05-31T07:07:17.903 に答える
5

個人的には、コンパイル時にリーダーのタイプがわかっている場合は、テンプレート ソリューションを使用することを好みます。これは、ここで行う実行時の決定がないため、ポリモーフィズムが役に立たないからです。ヘッダー ファイルにテンプレートを書き込むことに関する限り、リンカー エラーが発生しないようにする必要があります。これは、cpp でテンプレート メソッドを記述すると、コンパイラがテンプレート クラスをインスタンス化できず、リンカーがエラーを出すためです。回避策はいくつかありますが、ほとんどのテンプレート コードはヘッダー ファイルに記述されています。

于 2009-05-31T06:56:50.000 に答える
2

1.「最高」は相対的です。どちらの方法にも長所と短所があります。テンプレートは生の速度を提供しますが、より多くのコードが必然的にインライン化され (より多くの結合が生じる)、エラー メッセージが読みにくくなります。継承は遅くなり、オブジェクトが大きくなりますが、インライン化は必要ありません (結合が少ない)。また、比較的優れたエラー メッセージも表示されます。

小さなライブラリの場合、結合はそれほど重要ではなく、テンプレートが適切な選択になる可能性があります。ただし、ライブラリの複雑さが増すにつれて、結合の少ないアプローチに移行する必要があります。ライブラリがどれだけ大きくなるかわからない場合、またはテンプレートが提供する速度を必要としない場合 (またはエラー メッセージを処理したくない場合) は、継承を使用してください。

2 に対する私の回答は 1 の続きです。一部のコンシューマー テンプレートではインライン展開が必要なため、ヘッダーにコードを配置する必要があります。カップリングの質問です。インライン化により、コンポーネント間の結合が増加し、コンパイル時間が大幅に増加する可能性があります。速度が必要で、ライブラリが小さいままであることが確実でない限り、これは避けてください。

于 2009-05-31T06:38:40.690 に答える
0

GenericParserまたはParser?

コードの残りの部分に応じて、汎用パーサーの問題は、挿入するクラスもテンプレートでなければならないことです。
しかし、3番目のより一般的な方法があります... boost::functionとboost::lambda。要求する必要があるのは、function(クラスのユーザーの観点から)正しい戻りタイプとパラメーターを使用することだけです。boost::function< void ()> reader = bind( &TextFile::read, reader ); これで、ユーザークラスはリーダークラスから独立しており、テンプレートである必要はありません。

class User
{
const boost::function< void ()>& reader;

public:
    void setReader( const boost::function< void ()>& reader ) 
    : reader(reader) {
    }
};

.h / .cppの組み合わせではなく、すべてのコードをヘッダーファイルに書き込みます。

これは分離モデルと呼ばれ、それをサポートするコンパイラは1つだけです(Comeauコンパイラ)。「エクスポート」制限パート1および 「エクスポート」制限パート2を読み始めます。


@CiscoIPPhoneコメント:汎用パーサーの問題は、挿入するクラスもテンプレートでなければならないことです。

template<class T>
class GenericParser
{
public:
    GenericParser(T* p_reader) : reader(p_reader) { }

    void StartParsing()
    {
        reader->Read();
    }
private:
    T* reader;
};

// Now you have a GeniricParser Interface but your Parser is only usable for 
// TextFileReader 
class Parser
{
public:
    Parser( GenericParser<TextFileReader> p_reader) : reader(p_reader) { }
    void StartParsing() { 
        reader->Read();
    }
private:
    GenericParser<RealParser> reader;
};


//Solution is to make Parser also a template class
template<class T>
class Parser
{
public:
    Parser( GenericParser<T> p_reader) : reader(p_reader) { }
    void StartParsing() { 
        reader->Read();
    }
private:
    GenericParser<T> reader;
};
于 2009-05-31T07:00:42.533 に答える