web-dev-qa-db-ja.com

依存関係のクラスメンバーとして参照を使用する

メモリ管理された言語でしばらく過ごした後、C++に戻りますが、依存性注入を実装するための最良の方法が何であるかが突然わかりません。 (テスト駆動設計を非常に簡単にする最も簡単な方法であることがわかったので、私は完全にDIに売却されました)。

さて、ブラウジングSOとグーグルは私にこの問題についてかなりの数の意見をもらいました、そして私は少し混乱しています。

この質問への答えとして、 C++での依存性注入 、誰かが、依存性注入であっても、生のポインターを渡してはならないことを提案しました。オブジェクトの所有権に関連していることを理解しています。

現在、オブジェクトの所有権も(私の状態には十分な詳細ではありませんが;))悪名高いグーグルスタイルガイドで取り組まれています: http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml #Smart_Pointers

したがって、私が理解しているのは、どのオブジェクトが他のどのオブジェクトの所有権を持っているかを明確にするために、生のポインターを渡さないようにする必要があるということです。特に、この種のコーディングには反対のようです。

class Addict {
   // Something I depend on (hence, the Addict name. sorry.)
   Dependency * dependency_;
public:
   Addict(Dependency * dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_->do_something();
   }
   // ... whatever ... 
};    

依存関係が純粋仮想クラス(別名poor-man's-Interface)の場合、このコードを使用すると、依存関係のモックバージョンを簡単に挿入できます(google mockなどを使用)。

問題は、この種のコードで発生する可能性のある問題が実際には見られないことと、生のポインター以外のものを使用する必要がある理由です。依存関係がどこから来ているのかが明確ではないということですか?

また、私はこの状況で実際に参照を使用する必要があることを示唆するかなりの数の投稿を読んだので、この種のコードはより良いですか?

class Addict {
   // Something I depend on (hence, the Addict name. sorry.)
   const Dependency & dependency_;
  public:
   Addict(const Dependency & dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_.do_something();
   }
   // ... whatever ... 
};

しかし、その後、参照をメンバーとして使用して、他の同様に信頼できるアドバイスに対してを取得します: http://billharlan.com/pub/papers/Managing_Cpp_Objects.html

ご覧のとおり、さまざまなアプローチの相対的な長所と短所について正確にはわからないため、少し混乱しています。これが死ぬまで議論された場合、またはそれが特定のプロジェクト内の個人的な選択と一貫性の問題である場合は申し訳ありません...しかし、どんなアイデアでも歓迎します。


回答の概要

(これを行うのが良いSO-tiquetteかどうかはわかりませんが、回答から収集したもののコード例を追加します...)

さまざまな回答から、私の場合はおそらく次のようになります。

  • 依存関係を参照として渡します(少なくともNULLが不可能であることを確認するため)
  • コピーが不可能な一般的なケースでは、明示的に許可せず、依存関係を参照として保存します
  • コピーが可能なまれなケースでは、依存関係をRAWポインタとして保存します
  • 依存関係の作成者(ある種のファクトリ)に、スタック割り当てと動的割り当て(後者の場合はスマートポインターによる管理)のどちらかを決定させます。
  • 依存関係を独自のリソースから分離するための規則を確立する

だから私は次のようなものになってしまいます:

class NonCopyableAddict {
    Dependency & dep_dependency_;

    // Prevent copying
    NonCopyableAddict & operator = (const NonCopyableAddict & other) {}
    NonCopyableAddict(const NonCopyableAddict & other) {}

public:
    NonCopyableAddict(Dependency & dependency) : dep_dependency_(dep_dependency) {
    }
    ~NonCopyableAddict() {
      // No risk to try and delete the reference to dep_dependency_ ;)
    }
    //...
    void do_some_stuff() {
      dep_dependency_.some_function();
    }
};

そして、コピー可能なクラスの場合:

class CopyableAddict {
    Dependency * dep_dependency_;

public: 
    // Prevent copying
    CopyableAddict & operator = (const CopyableAddict & other) {
       // Do whatever makes sense ... or let the default operator work ? 
    }
    CopyableAddict(const CopyableAddict & other) {
       // Do whatever makes sense ...
    }


    CopyableAddict(Dependency & dependency) : dep_dependency_(&dep_dependency) {
    }
    ~CopyableAddict() {
      // You might be tempted to delete the pointer, but its name starts with dep_, 
      // so by convention you know it is not your job
    }
    //...
    void do_some_stuff() {
      dep_dependency_->some_function();
    }
};

私が理解したことから、コンパイラが強制できる「私はいくつかのものへのポインタを持っていますが、私はそれを所有していません」という意図を表現する方法はありません。したがって、ここでは命名規則に頼る必要があります...


参照用に保持

マーティンが指摘したように、次の例では問題は解決しません。

または、コピーコンストラクターがあるとすると、次のようになります。

class Addict {
   Dependency dependency_;
  public:
   Addict(const Dependency & dependency) : dependency_(dependency) {
   }
   ~Addict() {
     // Do NOT release dependency_, since it was injected and you don't own it !
   }
   void some_method() {
     dependency_.do_something();
   }
   // ... whatever ... 
};
25
phtrivier

厳格なルールはありません。
オブジェクト内で参照を使用するとコピーの問題が発生する可能性があると言われているように(実際に発生します)、万能薬ではありませんが、特定の状況では役立つ場合があります(C++では、すべてを実行するオプションが提供されます)。これらのさまざまな方法)。しかし、RAWポインタを使用することは実際にはオプションではありません。オブジェクトを動的に割り当てる場合は、常にスマートポインターを使用してオブジェクトを維持し、オブジェクトもスマートポインターを使用する必要があります。

例を要求する人のために:ストリームは常に渡され、参照として保存されます(コピーできないため)。

コード例に関するコメント:

例1と2

ポインタを使用した最初の例。基本的に、参照を使用した2番目の例と同じです。違いは、参照をNULLにすることはできないということです。参照を渡すと、オブジェクトはすでに存続しているため、テストしているオブジェクトよりも寿命が長くなるはずです(スタックで作成された場合)。したがって、参照を保持しても安全です。依存関係としてポインターを動的に作成している場合は、依存関係の所有権が共有されているかどうかに応じて、boost :: shared_pointerまたはstd :: auto_ptrの使用を検討します。

例3:

私はあなたの3番目の例の良い使用法を見ていません。これは、ポリモーフィック型を使用できないためです(Dependencyから派生したオブジェクトを渡すと、コピー操作中にスライスされます)。したがって、コードは別のクラスではなく、Addict内にある可能性があります。

ビル・ハーレン:( http://billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html

ビルから何も奪わないでくださいしかし:

  1. 彼のことは聞いたことがない。
    • 彼はコンピュータープログラマーではなくジオフィジストです
    • 彼はあなたのC++を改善するためにJavaでプログラミングすることを勧めます
    • 言語は現在、使用法が非常に異なっているため、まったく間違っています)。
    • やるべきこと/やらないことの参照を使用したい場合。
      次に、C++フィールドでビッグネームの1つを選択します。
      Stroustrup/Sutter/Alexandrescu/Meyers

概要:

  1. RAWポインタを使用しないでください(所有権が必要な場合)
  2. スマートポインタを使用してください。
  3. オブジェクトをオブジェクトにコピーしないでください(スライスされます)。
  4. 参照を使用できます(ただし、制限を知っています)。

参照を使用した依存性注入の私の例:

class Lexer
{
    public: Lexer(std::istream& input,std::ostream& errors);
    ... STUFF
    private:
       std::istream&  m_input;
       std::ostream&  m_errors;
};
class Parser
{
    public: Parser(Lexer& lexer);
    ..... STUFF
    private:
        Lexer&        m_lexer;
};

int main()
{
     CLexer  lexer(std::cin,std::cout);  // CLexer derived from Lexer
     CParser parser(lexer);              // CParser derived from Parser

     parser.parse();
}

// In test.cpp
int main()
{
     std::stringstream  testData("XXXXXX");
     std::stringstream  output;
     XLexer  lexer(testData,output);
     XParser parser(lexer);

     parser.parse();
}
9
Martin York

要約:参照を格納する必要がある場合は、ポインタをプライベート変数として格納し、それを逆参照するメソッドを介してアクセスします。オブジェクトの不変条件でポインタがnullでないことを確認することができます。

詳細:

まず、参照をクラスに格納すると、賢明で合法的なコピーコンストラクターまたは代入演算子を実装できなくなるため、それらを避ける必要があります。通常、使用するのは間違いです。

次に、関数とコンストラクターに渡されるポインター/参照のタイプは、オブジェクトを解放する責任があるのは誰か、およびオブジェクトを解放する方法を示す必要があります。

  • std :: auto_ptr-呼び出された関数は解放を担当し、解放が完了すると自動的に解放します。コピーセマンティクスが必要な場合、インターフェイスはauto_ptrを返すクローンメソッドを提供する必要があります。

  • std :: shared_ptr-呼び出された関数は解放を担当し、解放が完了したとき、およびオブジェクトへの他のすべての参照がなくなったときに自動的に解放します。浅いコピーのセマンティクスが必要な場合は、コンパイラが生成した関数で問題ありません。深いコピーが必要な場合は、インターフェイスに、shared_ptrを返すクローンメソッドを提供する必要があります。

  • 参照-呼び出し元には責任があります。あなたは気にしません-オブジェクトはあなたが知っているすべてのためにスタック割り当てされるかもしれません。この場合、参照で渡す必要がありますが、ポインタで保存。浅いコピーのセマンティクスが必要な場合は、コンパイラが生成した関数で問題ありません。深いコピーが必要な場合は、問題が発生します。

  • 生のポインタ。知るか?どこにでも割り当てることができます。 nullの可能性があります。あなたはそれを解放する責任があるかもしれません、あなたはそうではないかもしれません。

  • その他のスマートポインタ-ライフタイムを管理する必要がありますが、ドキュメントを参照して、コピーの要件を確認する必要があります。

オブジェクトを解放する責任を与えるメソッドはDIを壊さないことに注意してください-オブジェクトを解放することは、インターフェースとの契約の一部にすぎません(解放するために具体的なタイプについて何も知る必要がないため) )。

5
JoeG

[更新1]
依存関係が中毒者よりも長生きすることを常に保証できる場合は、もちろん、生のポインター/参照を使用しますcan。これら2つの間で、私は非常に単純な決定を行います。NULLが許可されている場合はポインター、それ以外の場合は参照します。

(私の元の投稿のポイントは、ポインターも参照も寿命の問題を解決しないということでした)


ここでは悪名高いグーグルスタイルのガイドラインに従い、スマートポインターを使用します。

ポインタと参照の両方に同じ問題があります。依存関係が中毒者よりも長生きすることを確認する必要があります。それはクライアントにかなり厄介な責任を押し付けます。

(参照カウント)スマートポインタを使用すると、ポリシーは依存関係が破棄され、誰もそれを使用しなくなります。私には完璧に聞こえます。

さらに良い方法:boost::shared_ptr(またはタイプニュートラルな破棄ポリシーを許可する同様のスマートポインター)を使用すると、ポリシーは構築時にオブジェクトにアタッチされます-これは通常、依存関係に影響を与えるすべてのものが1か所にまとめられることを意味します。

スマートポインタの典型的な問題(オーバーヘッド参照と循環参照)がここで発生することはめったにありません。依存関係のインスタンスは通常、小さくて多数ではなく、依存症者を強く参照している依存関係は、少なくともコードの臭いです。 (それでも、これらのことを覚えておく必要があります。C++へようこそ)

警告:私はDIに「完全に売られている」わけではありませんが、スマートポインターで完全に売られています;)

[更新2]
null削除機能を使用して、スタック/グローバルインスタンスにいつでもshared_ptrを作成できることに注意してください。
ただし、これをサポートするには双方が必要です。中毒者は、依存関係への参照を長生きする可能性のある他の誰かに転送しないことを保証する必要があり、発信者は生涯を保証する責任を負います。私はこの解決策に満足していませんが、時々これを使用しました。

1
peterchen

オブジェクトの1つをSTLコンテナーに貼り付けてしまうと、頭痛の種がなくなる傾向があるため、メンバーとしての参照は避けたいと思います。 boost::shared_ptr 所有権と boost::weak_ptr 扶養家族の場合。

1
D.Shawley

私はここで私のダウンモデレーションがすでに来ていることはできますが、いかなる理由であれ、クラスに参照メンバーがあってはならないと言います。単純な定数値である場合を除きます。これには多くの理由があります。これを開始すると、C++のすべての悪い点が開かれます。あなたが本当に気にかけているなら私のブログを見てください。

0

以前に質問されましたが、私のSO検索スキルではそれを見つけることができません。私の立場を要約すると、参照をクラスメンバーとして使用することはめったにありません。そうすると、すべてが発生します。初期化、割り当て、コピーの問題の一種です。代わりに、ポインタまたは値を使用してください。

編集:1つ見つかりました-これは回答としてさまざまな意見を持つ質問です: メンバーデータ内のポインターまたは参照を優先する必要がありますか?

0
anon

しかし、その後、メンバーとして参照を使用することに対して、他の同様に信頼できるアドバイスを受け取ります: http://billharlan.com/pub/papers/Managing%5FCpp%5FObjects.html

この場合、コンストラクターでオブジェクトを1回だけ設定し、変更しないでください。問題はありません。ただし、後で変更する場合は、init関数を使用し、コピーコンストラクターを用意します。つまり、参照を変更する必要があるすべてのものは、ポインターを使用する必要があります。

0
DaMacc