クラス MyClass を作成し、そのクラスに MyOtherClass というプライベート メンバーがある場合、MyOtherClass をポインターにするか、しない方がよいでしょうか? メモリ内のどこに格納されているかという点で、ポインターではないということはどういう意味ですか? クラスの作成時にオブジェクトが作成されますか?
QT の例では、通常、クラス メンバがクラスである場合、クラス メンバをポインタとして宣言していることに気付きました。
クラス MyClass を作成し、そのクラスに MyOtherClass というプライベート メンバーがある場合、MyOtherClass をポインターにするか、しない方がよいでしょうか? メモリ内のどこに格納されているかという点で、ポインターではないということはどういう意味ですか? クラスの作成時にオブジェクトが作成されますか?
QT の例では、通常、クラス メンバがクラスである場合、クラス メンバをポインタとして宣言していることに気付きました。
クラス MyClass を作成し、それに MyOtherClass というプライベート メンバーがある場合、MyOtherClass をポインターにするかどうかを選択します。
通常、クラスの値として宣言する必要があります。それはローカルになり、エラーの可能性が少なくなり、割り当てが少なくなります-最終的には、問題が発生する可能性が少なくなり、コンパイラーは指定されたオフセットにあることを常に認識できるため...最適化とバイナリ削減に役立ちますいくつかのレベル。ポインターを処理する必要があることがわかっている場合 (つまり、ポリモーフィック、共有、再割り当てが必要) がいくつかありますが、通常は、必要な場合にのみポインターを使用することをお勧めします。特に、プライベート/カプセル化されている場合は特にそうです。
メモリ内のどこに格納されているかという点で、ポインターではないということはどういう意味ですか?
そのアドレスは近い (または等しい) this
-- gcc (たとえば) には、クラス データ (サイズ、vtables、オフセット) をダンプするための高度なオプションがあります。
クラスの作成時にオブジェクトが作成されますか?
はい - MyClass のサイズは sizeof(MyOtherClass) だけ大きくなるか、コンパイラがそれを再配置する場合はそれ以上になります (たとえば、自然な配置に)
次の例を見てください。
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のメンバーを持ちます。foo
Foo
B
foo
pointer to Foo
の状況は何ですかA
:
a_stack
にタイプA
の変数を作成すると、オブジェクト (明らかに) とそのメンバーもスタックに置かれます。A
上記の例でlikeへのポインターを作成するとa_heap
、ポインター変数だけがスタックに置かれます。他のすべて (オブジェクトとそのメンバー) はheapにあります。次の場合、状況はどのように見えますかB
?
B
上に作成する場合: オブジェクトとそのメンバーの両方がスタック上にありますが、 (ポインティー)を指すオブジェクトはヒープ上にあります。つまり、(ポインター) はスタック上にありますが、(ポインティー) はヒープ上にあります。foo
foo
b_stack.foo
*b_stack.foo
B
namedへのポインターを作成しますb_heap
: b_heap
(ポインター) はスタック上にあり、*b_heap
(pointee) はheapとメンバーb_heap->foo
and にあり*b_heap->foo
ます。foo
の暗黙のデフォルト コンストラクターを呼び出すことによって自動的に作成されますFoo
。これにより が作成されますinteger
が、初期化はされません(乱数が含まれます)。foo
(ポインター) を省略すると、乱数で作成および初期化されます。これは、ヒープ上のランダムな場所を指すことを意味します。ただし、ポインタが存在することに注意してください。また、暗黙のデフォルトコンストラクターは何かを割り当てないことに注意してください。foo
これは明示的に行う必要があります。そのため、通常、明示的なコンストラクターとそれに付随するデストラクターを使用して、メンバー ポインターの指す先を割り当てて削除する必要があります。コピーのセマンティクスを忘れないでください: (コピーの構築または代入を介して) オブジェクトをコピーすると、ポインティング先はどうなりますか?メンバーへのポインターを使用するユースケースがいくつかあります。
メンバーがポインターであり、それらを所有している場合は特に注意してください。適切なコンストラクタ、デストラクタを作成し、コピー コンストラクタと代入演算子について考える必要があります。オブジェクトをコピーすると、ポインティング先はどうなりますか? 通常、ポインティもコピーして作成する必要があります。
C++ では、ポインターはそれ自体がオブジェクトです。それらは、それらが指し示すものに実際には結び付けられておらず、ポインターとその指先の間に特別な相互作用はありません (それは単語ですか?)
ポインターを作成すると、ポインターだけが作成され、他には何も作成されません。それが指しているかもしれないし、指していないかもしれないオブジェクトを作成しません。また、ポインターがスコープ外に出ても、ポイント先のオブジェクトは影響を受けません。ポインターは、それが指しているものの寿命にはまったく影響しません。
したがって、一般に、デフォルトではポインターを使用しないでください。クラスに別のオブジェクトが含まれている場合、そのオブジェクトはポインターであってはなりません。
ただし、クラスが別のオブジェクトを認識している場合は、それを表すにはポインターが適している可能性があります (クラスの複数のインスタンスが、所有権を取得したり、その有効期間を制御したりすることなく、同じインスタンスを指すことができるため)
C++ の一般的な知恵は、(そのままの) ポインターの使用をできるだけ避けることです。特に、動的に割り当てられたメモリを指すベア ポインター。
その理由は、特に例外がスローされる可能性も考慮する必要がある場合に、ポインターを使用すると堅牢なクラスを作成するのが難しくなるためです。
私は次のルールに従います: メンバー オブジェクトがカプセル化オブジェクトと共に存続し、終了する場合は、ポインターを使用しないでください。何らかの理由でメンバーオブジェクトがカプセル化オブジェクトよりも長く存続する必要がある場合は、ポインターが必要になります。手元のタスクによって異なります。
通常、メンバ オブジェクトが与えられ、作成されていない場合は、ポインタを使用します。その後、通常はそれを破壊する必要もありません。
この質問は際限なく熟考される可能性がありますが、基本は次のとおりです。
MyOtherClass がポインターでない場合:
MyOtherClass がポインターの場合:
NULL
、コンテキストで意味を持つ可能性があり、メモリを節約できる可能性がありますポインター メンバーのいくつかの利点:
メンバーをオブジェクトとして持つ利点:
メンバーオブジェクトへの(std :: auto_ptr)ポインターとしてメンバーオブジェクトとの関係を維持する親クラスの利点は、オブジェクトのヘッダーファイルをインクルードする必要がなく、オブジェクトを前方宣言できることです。
これにより、ビルド時にクラスが分離され、親クラスのすべてのクライアントがメンバーオブジェクトの関数にアクセスしない場合でも、親クラスのすべてのクライアントを再コンパイルすることなく、メンバーオブジェクトのヘッダークラスを変更できます。
auto_ptrを使用する場合、必要なのは構築のみです。これは通常、初期化子リストで実行できます。親オブジェクトとともに破壊は、auto_ptrによって保証されます。
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 から派生した他のクラスを柔軟に指すことができるからです。基本的に、ダイナミクス ポリモーフィズムの実装に役立ちます。
場合によります... :-)
ポインターを使用して a を言う場合class A
、クラスのコンストラクターなどでタイプ A のオブジェクトを作成する必要があります
m_pA = new A();
さらに、デストラクタでオブジェクトを破棄することを忘れないでください。そうしないと、メモリ リークが発生します。
delete m_pA;
m_pA = NULL;
代わりに、タイプ A のオブジェクトをクラスに集約する方が簡単です。オブジェクトの有効期間の終わりに自動的に行われるため、破棄することを忘れることはできません。
一方、ポインタを持つことには次の利点があります。
オブジェクトがスタックに割り当てられ、タイプ A が大量のメモリを使用する場合、これはスタックからではなくヒープから割り当てられます。
A オブジェクトを後で (例: method で) 構築するCreate
か、以前に ( method でClose
)破棄することができます。
簡単なことは、メンバーをオブジェクトとして宣言することです。このように、コピーの構築、破棄、割り当てを気にする必要はありません。これはすべて自動的に処理されます。
ただし、ポインターが必要な場合もあります。結局、マネージ言語 (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 のどこかにリソースがたくさんあります)。