クラスメンバーとしての参照メンバー変数


85

私の職場では、このスタイルが広く使用されているのがわかります。-

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};


int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

このイディオムを説明する名前はありますか?大きな複雑なオブジェクトをコピーすることによるおそらく大きなオーバーヘッドを防ぐためだと思いますか?

これは一般的に良い習慣ですか?このアプローチに落とし穴はありますか?


4
考えられる落とし穴の1つは、メンバー変数で参照しているオブジェクトが他の場所で破棄され、クラスを介してアクセスしようとした場合です
mathematician1975

回答:


116

このイディオムを説明する名前はありますか?

UMLでは、これは集約と呼ばれます。メンバーオブジェクトが参照クラスによって所有されていないという点で、構成とは異なります。C ++では、参照またはポインターを使用して、2つの異なる方法で集計を実装できます。

大きな複雑なオブジェクトをコピーすることによるおそらく大きなオーバーヘッドを防ぐためだと思いますか?

いいえ、それはこれを使用する本当に悪い理由になります。集約の主な理由は、含まれているオブジェクトが含まれているオブジェクトによって所有されていないため、それらの存続期間が制限されていないことです。特に、参照されるオブジェクトの存続期間は、参照するオブジェクトの存続期間よりも長くなければなりません。それははるかに早く作成された可能性があり、コンテナの寿命の終わりを超えて存続する可能性があります。さらに、参照されるオブジェクトの状態はクラスによって制御されませんが、外部で変更できます。参照がでない場合、constクラスはその外部にあるオブジェクトの状態を変更できます。

これは一般的に良い習慣ですか?このアプローチに落とし穴はありますか?

デザインツールです。良いアイデアになる場合もあれば、そうでない場合もあります。最も一般的な落とし穴は、参照を保持しているオブジェクトの存続期間が、参照されているオブジェクトの存続期間を超えてはならないことです。参照さたオブジェクトが破棄された後に、囲んでいるオブジェクトが参照を使用する場合、未定義の動作が発生します。一般に、集約よりも構成を優先する方が良いですが、必要な場合は、他のツールと同じくらい優れたツールです。


7
「いいえ、それはこれを使用する本当に悪い理由でしょう」。この点について詳しく教えていただけますか?それを達成するために代わりに何を使用できますか?
コインコイン2016年

@coincoin:正確に何を達成するために?
デビッド・ロドリゲス-ドリビアス2016年

3
to prevent the possibly large overhead of copying a big complex object?
コインコイン2016年

3
@underscore_dご回答ありがとうございます。次に、どちらも使用できない場合はどうなりますか。異なるクラス内で同じオブジェクトを共有したいとします。このメンバーオブジェクトを値で渡すと、各クラスのオブジェクトのコピーが作成されますか?したがって、解決策は、コピーを回避するためにスマートポインタまたは参照を使用することです。番号 ?
コインコイン2016年

2
@ plats1それは私がちょうど書いたものです私はそれを知っています。私のポイントは、スマートポインタまたは参照のいずれかを使用できるということです。
コインコイン2017年

37

これは、コンストラクター注入による依存性注入と呼ばれます。クラスAは、コンストラクターへの引数として依存性を取得し、依存クラスへの参照をプライベート変数として保存します。

ウィキペディアに興味深い紹介があります。

const-correctnessについては、次のように記述します。

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

ただし、このクラスの問題は、一時オブジェクトへの参照を受け入れることです。

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

追加することをお勧めします(少なくともC ++ 11が必要です):

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

とにかく、コンストラクターを変更した場合:

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

一時的なへのポインタがないことはほぼ保証されています

また、コンストラクターはポインターを受け取るためA、渡すオブジェクトの存続期間に注意を払う必要があることはユーザーにとってより明確です。


やや関連するトピックは次のとおりです。


20

このイディオムを説明する名前はありますか?

この使用法の名前はありません。単に「クラスメンバーとしての参照」と呼ばれます。

大きな複雑なオブジェクトをコピーすることによるおそらく大きなオーバーヘッドを防ぐためだと思いますか?

はい。また、あるオブジェクトの存続期間を別のオブジェクトに関連付けたいシナリオもあります。

これは一般的に良い習慣ですか?このアプローチに落とし穴はありますか?

用途によって異なります。言語機能を使用することは、「コースに馬を選ぶ」ようなものです。いくつかのシナリオで役立つため、すべての(ほとんどすべての)言語機能が存在することに注意することが重要です。
参照をクラスメンバーとして使用する場合、注意すべき重要な点がいくつかあります。

  • クラスオブジェクトが存在するまで、参照されるオブジェクトが存在することが保証されていることを確認する必要があります。
  • コンストラクターメンバー初期化子リストのメンバーを初期化する必要があります。遅延初期化を行うことはできません。これは、ポインターメンバーの場合に可能です。
  • コンパイラーはコピー割り当てoperator=()を生成しないため、自分でコピー割り当てを提供する必要があります。この=ような場合にオペレーターがどのような行動を取るかを決めるのは面倒です。したがって、基本的にクラスは割り当て不可になります。
  • NULL他のオブジェクトを参照することはできません。再装着が必要な場合は、ポインタの場合のように参照を使用することはできません。

ほとんどの実用的な目的では(メンバーのサイズが原因でメモリ使用量が多いことを本当に心配している場合を除き)、ポインターや参照メンバーの代わりにメンバーインスタンスを用意するだけで十分です。これにより、余分なメモリ使用量を犠牲にして、参照/ポインタメンバーがもたらす他の問題について心配する必要がなくなります。

ポインターを使用する必要がある場合は、生のポインターの代わりにスマートポインターを使用してください。それはあなたの人生をポインターではるかに楽にするでしょう。


大きな複雑なオブジェクトをコピーすることによる大きなオーバーヘッドを防ぐためだと思いますか?はい」-私には何も見えないので、このパターンがコピーの回避に関連すると思う理由を詳しく説明してください。この非「イディオム」が実際の目的の副作用として誰かのためにコピーを保存する場合、元のデザインは最初から致命的な欠陥があり、このパターンに置き換えるだけでは期待どおりの結果が得られない可能性があります。 。
underscore_d

@underscore_dクラスに重要な量のconstデータが必要であり、同時にこのクラスのインスタンスが多数存在する可能性がある場合、各インスタンスがそのconstデータの独自のコピーを持つことは容認できないほど無駄になる可能性があります。したがって、共有できるデータの外部の場所へのconst参照を保存すると、多くのコピーを節約できます。データハンドルは必ずしも動的に割り当てられる必要がないため、shared_ptrは必ずしもソリューションではありません。
重要ではない

@Unimportant 2016年に私の異議が何であったかはわかりません。私は、常に参照をクラスメンバーとして使用しています。値による所有権を参照に置き換えるだけでは生涯にわたる質問が発生し、必ずしもパナケイアや常に1:1で実行できるものではないのではないかと心配していました。私は知らないよ。
underscore_d

1

C ++は、クラス/構造体構造体を介してオブジェクトの存続期間を管理するための優れたメカニズムを提供します。これは、他の言語に対するC ++の優れた機能の1つです。

refまたはpointerを介してメンバー変数を公開すると、原則としてカプセル化に違反します。このイディオムにより、クラスのコンシューマーは、Aのオブジェクトの状態を、Aのオブジェクトの知識や制御がなくても変更できます。また、消費者は、Aのオブジェクトの存続期間を超えて、Aの内部状態への参照/ポインターを保持することができます。これは悪い設計です。代わりに、クラスをリファクタリングして、共有オブジェクトへの参照/ポインターを保持し(所有していない)、コンストラクターを使用して設定することができます(ライフタイムルールの義務化)。共有オブジェクトのクラスは、場合によってはマルチスレッド/同時実行をサポートするように設計されている場合があります。


2
ただし、OPのコードは、メンバー変数への参照を保持していません。参照であるメンバー変数があります。だから、素晴らしい点ですが、一見無関係に見えます。
underscore_d

-3

メンバーの参照は通常、悪いと見なされます。彼らはメンバーのポインターと比較して人生を困難にします。しかし、それは特に珍しいことではなく、特別な名前のイディオムやものでもありません。エイリアシングです。


23
通常は悪いと見なされるサポートへの参照を提供できますか?
デビッド・ロドリゲス-ドリビアス2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.