50

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

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

4

11 に答える 11

35

クラス MyClass を作成し、それに MyOtherClass というプライベート メンバーがある場合、MyOtherClass をポインターにするかどうかを選択します。

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

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

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

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

はい - MyClass のサイズは sizeof(MyOtherClass) だけ大きくなるか、コンパイラがそれを再配置する場合はそれ以上になります (たとえば、自然な配置に)

于 2010-10-06T10:35:07.467 に答える
29

あなたのメンバーはメモリのどこに保存されていますか?

次の例を見てください。

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 つのクラスを定義AしますB。typeAの public メンバーを格納します。はtypeのメンバーを持ちます。fooFooBfoopointer to Foo

の状況は何ですかA:

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

次の場合、状況はどのように見えますかB?

  • スタックB上に作成する場合: オブジェクトとそのメンバーの両方がスタック上にありますが、 (ポインティー)を指すオブジェクトはヒープ上にあります。つまり、(ポインター) はスタック上にありますが、(ポインティー) はヒープ上にあります。foofoob_stack.foo*b_stack.foo
  • Bnamedへのポインターを作成しますb_heap: b_heap(ポインター) はスタック上にあり、*b_heap(pointee) はheapとメンバーb_heap->fooand にあり*b_heap->fooます。

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

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

このすべてのポイントは何ですか?

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

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

アドバイス

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

于 2010-10-06T11:05:42.810 に答える
19

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

ポインターを作成すると、ポインターだけが作成され、他には何も作成されません。それが指しているかもしれないし、指していないかもしれないオブジェクトを作成しません。また、ポインターがスコープ外に出ても、ポイント先のオブジェクトは影響を受けません。ポインターは、それが指しているものの寿命にはまったく影響しません。

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

ただし、クラス別のオブジェクトを認識している場合は、それを表すにはポインターが適している可能性があります (クラスの複数のインスタンスが、所有権を取得したり、その有効期間を制御したりすることなく、同じインスタンスを指すことができるため)

于 2010-10-06T10:19:23.733 に答える
8

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

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

于 2010-10-06T10:23:55.343 に答える
5

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

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

于 2010-10-06T10:29:32.973 に答える
4

この質問は際限なく熟考される可能性がありますが、基本は次のとおりです。

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

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

MyOtherClass がポインターの場合:

  • MyOtherClass の作成と破棄はあなたの責任です
  • MyOtherClass はNULL、コンテキストで意味を持つ可能性があり、メモリを節約できる可能性があります
  • MyClass の 2 つのインスタンスが同じ MyOtherClass を共有できます
于 2010-10-06T10:24:34.067 に答える
4

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

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

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

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

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

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

auto_ptrを使用する場合、必要なのは構築のみです。これは通常、初期化子リストで実行できます。親オブジェクトとともに破壊は、auto_ptrによって保証されます。

于 2010-10-06T11:26:46.603 に答える
1

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 をポインター メンバーとして保持することをお勧めします。これにより、MyOtherClass から派生した他のクラスを柔軟に指すことができるからです。基本的に、ダイナミクス ポリモーフィズムの実装に役立ちます。

于 2010-10-06T10:23:56.850 に答える
1

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

ポインターを使用して a を言う場合class A、クラスのコンストラクターなどでタイプ A のオブジェクトを作成する必要があります

 m_pA = new A();

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

delete m_pA; 
m_pA = NULL;

代わりに、タイプ A のオブジェクトをクラスに集約する方が簡単です。オブジェクトの有効期間の終わりに自動的に行われるため、破棄することを忘れることはできません。

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

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

  • A オブジェクトを後で (例: method で) 構築するCreateか、以前に ( method でClose)破棄することができます。

于 2010-10-06T10:29:53.380 に答える
0

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

ただし、ポインターが必要な場合もあります。結局、マネージ言語 (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 のどこかにリソースがたくさんあります)。

于 2010-10-06T11:49:30.957 に答える