web-dev-qa-db-ja.com

オブジェクトであるクラスメンバー-ポインターかどうか? C ++

クラスMyClassを作成し、それにいくつかのプライベートメンバーがMyOtherClassと言っている場合、MyOtherClassをポインターにするか、しない方が良いですか?それがメモリのどこに格納されているかという点で、ポインタではないということはどういう意味ですか?クラスの作成時にオブジェクトは作成されますか?

QTの例では、通常、クラスメンバーがクラスである場合、それらをポインターとして宣言していることに気付きました。

よろしく

マーク

42
Mark

クラスMyClassを作成し、それにいくつかのプライベートメンバーがMyOtherClassと言っている場合、MyOtherClassをポインターにするか、しない方が良いですか?

通常は、クラスの値として宣言する必要があります。それはローカルになり、エラーの可能性が減り、割り当てが減ります-最終的には問題が発生する可能性が少なくなり、コンパイラーは指定されたオフセットにあることを常に知ることができるので...いくつかのレベル。ポインターを処理する必要があることがわかっている場合がいくつかあります(つまり、ポリモーフィック、共有、再割り当てが必要)。通常、特にプライベート/カプセル化されている場合は、必要なときにのみポインターを使用するのが最善です。

それがメモリのどこに格納されているかという点で、ポインタではないということはどういう意味ですか?

そのアドレスはthisに近い(または等しい)--gcc(たとえば)には、クラスデータ(サイズ、vtables、オフセット)をダンプするためのいくつかの高度なオプションがあります

クラスの作成時にオブジェクトは作成されますか?

はい-MyClassのサイズは、sizeof(MyOtherClass)、またはコンパイラーが再調整した場合(たとえば、その自然な調整に合わせて)増加します

32
justin

メンバーはメモリのどこに保存されますか?

この例を見てみましょう:

struct Foo { int m; };
struct A {
  Foo foo;
};
struct B {
  Foo *foo;
  B() : foo(new Foo()) { } // ctor: allocate Foo on heap
  ~B() { delete foo; } // dtor: Don't forget this!
};

void bar() {
  A a_stack; // a_stack is on stack
             // a_stack.foo is on stack too
  A* a_heap = new A(); // a_heap is on stack (it's a pointer)
                       // *a_heap (the pointee) is on heap
                       // a_heap->foo is on heap
  B b_stack; // b_stack is on stack
             // b_stack.foo is on stack
             // *b_stack.foo is on heap
  B* b_heap = new B(); // b_heap is on stack
                       // *b_heap is on heap
                       // b_heap->foo is on heap
                       // *(b_heap->foo is on heap
  delete a_heap;
  delete b_heap;
  // B::~B() will delete b_heap->foo!
} 

2つのクラスABを定義します。 Aは、タイプfooのパブリックメンバーFooを格納します。 Bには、タイプpointer to Fooのメンバーfooがあります。

Aの状況:

  • stackにタイプAの変数a_stackを作成すると、オブジェクト(明らかに)とそのメンバーはスタックにもあります。
  • 上記の例のa_heapのようにAへのポインターを作成する場合、ポインター変数だけがstack;にあります。その他すべて(オブジェクトとそのメンバー)はheapにあります。

Bの場合、状況はどのようになりますか:

  • stackBを作成すると、オブジェクトとそのメンバーfooの両方がstack、ただしfooが(指示先)を指すオブジェクトはheap。つまり、b_stack.foo(ポインタ)はスタック上にありますが、*b_stack.foo(ポインタ先)はヒープ上にあります。
  • b_heapという名前のBへのポインターを作成します。b_heap(ポインター)はスタックにあり、*b_heap(指示先)はheap、およびメンバーb_heap->fooおよび*b_heap->foo

オブジェクトは自動的に作成されますか?

  • Aの場合:はい、fooの暗黙のデフォルトコンストラクターを呼び出すことにより、Fooが自動的に作成されます。これはintegerを作成しますが、初期化しますしません(乱数があります)!
  • Bの場合:ctorとdtorを省略すると、foo(ポインター)も作成され、乱数で初期化されます。これは、を指すことを意味しますヒープ上のランダムな場所。しかし、ポインタが存在することに注意してください!また、暗黙のデフォルトコンストラクターはfooに何かを割り当てないので、これを行う必要がありますexplicitly。そのため、通常明示的なコンストラクターとそれに付随するデストラクターが必要です。メンバーポインターの指示先を削除します。 copy semanticsを忘れないでください:オブジェクトをコピーすると(コピーの構築または割り当てを介して)、指示先はどうなりますか?

このすべての意味は何ですか?

メンバーへのポインターを使用するいくつかの使用例があります。

  • 所有していないオブジェクトを指す。クラスがコピーに非常にコストのかかる巨大なデータ構造にアクセスする必要があるとしましょう。次に、このデータ構造へのポインタを保存します。この場合、データ構造のcreationおよびdeletionがクラスの範囲外。他の誰かが世話をする必要があります。
  • ヘッダーファイルで指示先を定義する必要がないため、コンパイル時間が長くなります。
  • もう少し高度です。クラスにすべてのプライベートメンバーを格納する別のクラスへのポインターがある場合、「Pimplイディオム」: http://c2.com/cgi/wiki?PimplIdiom 、Sutter、Hも見てください。 。(2000):Exceptional C++、p。 99--119
  • そして他のいくつかは、他の答えを見てください

助言

メンバーがポインターであり、それらを所有している場合は、特に注意してください。適切なコンストラクタ、デストラクタを記述し、コピーコンストラクタと代入演算子について考える必要があります。オブジェクトをコピーすると、指示先はどうなりますか?通常は、先の先もコピーして作成する必要があります。

27
WolfgangP

C++では、ポインターはそれ自体がオブジェクトです。それらは実際に指すものに何も関連付けられておらず、ポインターとその指示先との間に特別な相互作用はありません(それは単語ですか?)

ポインタを作成する場合、ポインタを作成しますおよびそれ以外。それが指している、または指し示していない可能性のあるオブジェクトは作成しません。また、ポインターがスコープの外に出た場合、ポイントされたオブジェクトは影響を受けません。ポインタは、それが指すものの寿命に影響を与えることはありません。

したがって、一般的には、デフォルトでnotポインタを使用する必要があります。クラスに別のオブジェクトが含まれている場合、その別のオブジェクトはポインターであってはなりません。

ただし、クラスについて知っている別のオブジェクトの場合、ポインターはそれを表すための良い方法です(クラスの複数のインスタンスが所有権を取得せずに同じインスタンスを指すことができるため、その寿命を制御せずに)

18
jalf

C++での一般的な知識は、(ベア)ポインターの使用を可能な限り避けることです。特に、動的に割り当てられたメモリを指すベアポインター。

その理由は、特に例外がスローされる可能性も考慮する必要がある場合は、ポインターによって堅牢なクラスを作成することが難しくなるためです。

ポインターメンバーのいくつかの利点:

  • 子(MyOtherClass)オブジェクトは、その親(MyClass)とは異なる有効期間を持つことができます。
  • オブジェクトは、複数のMyClass(または他の)オブジェクト間で共有される可能性があります。
  • MyClassのヘッダーファイルをコンパイルする場合、コンパイラーは必ずしもMyOtherClassの定義を知っている必要はありません。ヘッダーを含める必要がないため、コンパイル時間が短縮されます。
  • MyClassのサイズを小さくします。コードがMyClassオブジェクトのコピーを大量に実行する場合、これはパフォーマンスにとって重要です。 MyOtherClassポインタをコピーして、ある種の参照カウントシステムを実装できます。

メンバーをオブジェクトとして持つ利点:

  • オブジェクトを作成および破棄するためのコードを明示的に記述する必要はありません。簡単でエラーが発生しにくくなります。
  • 2つではなく1つのメモリブロックのみを割り当てる必要があるため、メモリ管理がより効率的になります。
  • 代入演算子、コピー/移動コンストラクタなどの実装は、はるかに簡単です。
  • より直感的
4
Timo

この質問は延々と審議される可能性がありますが、基本は次のとおりです。

MyOtherClassがポインターでない場合:

  • MyOtherClassの作成と破棄は自動的に行われるため、バグを減らすことができます。
  • MyOtherClassが使用するメモリはMyClassInstanceに対してローカルであり、パフォーマンスを向上させることができます。

MyOtherClassがポインターの場合:

  • MyOtherClassの作成と破棄はあなたの責任です
  • MyOtherClassはNULLになる場合があります。これは、コンテキストで意味があり、メモリを節約できます。
  • MyClassの2つのインスタンスが同じMyOtherClassを共有できます
4
Drew Dormann

私は次のルールに従います:メンバーオブジェクトがカプセル化オブジェクトと共に存続し、死ぬ場合は、ポインターを使用しないでください。メンバーオブジェクトが何らかの理由でカプセル化オブジェクトよりも長く存続する必要がある場合は、ポインターが必要になります。手元のタスクに依存します。

通常、メンバーオブジェクトが与えられ、作成されていない場合は、ポインターを使用します。そうすれば、通常はそれを破壊する必要もありません。

4
chvor

MyOtherClassオブジェクトをMyClassのメンバーとして作成した場合:

size of MyClass = size of MyClass + size of MyOtherClass

MyOtherClassオブジェクトをMyClassのポインターメンバーとして作成した場合:

size of MyClass = size of MyClass + size of any pointer on your system

MyOtherClassは、それから派生する他のクラスを指すように柔軟に指定できるため、ポインターメンバーとして保持することをお勧めします。基本的に、動的ポリモーフィズムの実装を支援します。

1
Alok Save

場合によります... :-)

ポインタを使用してclass A、タイプAのオブジェクトを作成する必要があります。クラスのコンストラクター

 m_pA = new A();

さらに、デストラクタでオブジェクトを破棄することを忘れないでください。そうしないと、メモリリークが発生します。

delete m_pA; 
m_pA = NULL;

代わりに、タイプAのオブジェクトをクラスに集約する方が簡単です。オブジェクトの存続期間の終了時に自動的に行われるため、オブジェクトを破棄することを忘れないでください。

一方、ポインタを持つことには、次の利点があります。

  • オブジェクトがスタックに割り当てられ、タイプAが大量のメモリを使用する場合、これはスタックではなくヒープから割り当てられます。

  • Aオブジェクトを後で(たとえば、メソッドCreateで)構築することも、早期に破棄することもできます(メソッドCloseで)

1
ur.

メンバーオブジェクトへの関係をメンバーオブジェクトへの(std :: auto_ptr)ポインターとして維持する親クラスの利点は、オブジェクトのヘッダーファイルを含める必要がなく、オブジェクトを前方宣言できることです。

これにより、ビルド時にクラスが分離され、メンバーオブジェクトの関数にアクセスしない場合でも、親クラスのすべてのクライアントを再コンパイルすることなく、メンバーオブジェクトのヘッダークラ​​スを変更できます。

Auto_ptrを使用する場合は、通常は初期化子リストで行うことができる構成を処理するだけで済みます。破壊は親オブジェクトとともにauto_ptrによって保証されます。

1
andreas buykx

簡単なことは、メンバーをオブジェクトとして宣言することです。この方法では、コピーの構築、破棄、割り当てを気にする必要はありません。これはすべて自動的に処理されます。

ただし、ポインタが必要な場合もあります。結局のところ、マネージ言語(C#やJavaなど)は実際にはポインターによってメンバーオブジェクトを保持しています。

最も明白なケースは、保持されるオブジェクトが多態性である場合です。 Qtでは、ご指摘のとおり、ほとんどのオブジェクトはポリモーフィッククラスの巨大な階層に属しており、メンバーオブジェクトのサイズが事前にわからないため、ポインターによるオブジェクトの保持は必須です。

この場合、特にジェネリッククラスを扱う場合は、いくつかの一般的な落とし穴に注意してください。例外の安全性は大きな懸念事項です。

struct Foo
{
    Foo() 
    {
        bar_ = new Bar();
        baz_ = new Baz(); // If this line throw, bar_ is never reclaimed
                          // See copy constructor for a workaround
    }

    Foo(Foo const& x)
    {
        bar_ = x.bar_.clone();
        try { baz_ = x.baz_.clone(); }
        catch (...) { delete bar_; throw; }
    }

    // Copy and swap idiom is perfect for this.
    // It yields exception safe operator= if the copy constructor
    // is exception safe.
    void swap(Foo& x) throw()
    { std::swap(bar_, x.bar_); std::swap(baz_, x.baz_); }

    Foo& operator=(Foo x) { x.swap(*this); return *this; }

private:
    Bar* bar_;
    Baz* baz_;
};

ご覧のとおり、ポインターが存在する場合に例外セーフコンストラクターを用意するのは非常に面倒です。 RAIIとスマートポインタを確認する必要があります(ここおよびWebの他の場所には多くのリソースがあります)。

0
Alexandre C.