クラス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
を定義します。 A
は、タイプfoo
のパブリックメンバーFoo
を格納します。 B
には、タイプpointer to Foo
のメンバーfoo
があります。
A
の状況:
A
の変数a_stack
を作成すると、オブジェクト(明らかに)とそのメンバーはスタックにもあります。a_heap
のようにA
へのポインターを作成する場合、ポインター変数だけがstack;にあります。その他すべて(オブジェクトとそのメンバー)はheapにあります。B
の場合、状況はどのようになりますか:
B
を作成すると、オブジェクトとそのメンバーfoo
の両方がstack、ただしfoo
が(指示先)を指すオブジェクトはheap。つまり、b_stack.foo
(ポインタ)はスタック上にありますが、*b_stack.foo
(ポインタ先)はヒープ上にあります。b_heap
という名前のB
へのポインターを作成します。b_heap
(ポインター)はスタックにあり、*b_heap
(指示先)はheap、およびメンバーb_heap->foo
および*b_heap->foo
。foo
の暗黙のデフォルトコンストラクターを呼び出すことにより、Foo
が自動的に作成されます。これはinteger
を作成しますが、初期化しますしません(乱数があります)!foo
(ポインター)も作成され、乱数で初期化されます。これは、を指すことを意味しますヒープ上のランダムな場所。しかし、ポインタが存在することに注意してください!また、暗黙のデフォルトコンストラクターはfoo
に何かを割り当てないので、これを行う必要がありますexplicitly。そのため、通常明示的なコンストラクターとそれに付随するデストラクターが必要です。メンバーポインターの指示先を削除します。 copy semanticsを忘れないでください:オブジェクトをコピーすると(コピーの構築または割り当てを介して)、指示先はどうなりますか?メンバーへのポインターを使用するいくつかの使用例があります。
メンバーがポインターであり、それらを所有している場合は、特に注意してください。適切なコンストラクタ、デストラクタを記述し、コピーコンストラクタと代入演算子について考える必要があります。オブジェクトをコピーすると、指示先はどうなりますか?通常は、先の先もコピーして作成する必要があります。
C++では、ポインターはそれ自体がオブジェクトです。それらは実際に指すものに何も関連付けられておらず、ポインターとその指示先との間に特別な相互作用はありません(それは単語ですか?)
ポインタを作成する場合、ポインタを作成しますおよびそれ以外。それが指している、または指し示していない可能性のあるオブジェクトは作成しません。また、ポインターがスコープの外に出た場合、ポイントされたオブジェクトは影響を受けません。ポインタは、それが指すものの寿命に影響を与えることはありません。
したがって、一般的には、デフォルトでnotポインタを使用する必要があります。クラスに別のオブジェクトが含まれている場合、その別のオブジェクトはポインターであってはなりません。
ただし、クラスについて知っている別のオブジェクトの場合、ポインターはそれを表すための良い方法です(クラスの複数のインスタンスが所有権を取得せずに同じインスタンスを指すことができるため、その寿命を制御せずに)
C++での一般的な知識は、(ベア)ポインターの使用を可能な限り避けることです。特に、動的に割り当てられたメモリを指すベアポインター。
その理由は、特に例外がスローされる可能性も考慮する必要がある場合は、ポインターによって堅牢なクラスを作成することが難しくなるためです。
ポインターメンバーのいくつかの利点:
メンバーをオブジェクトとして持つ利点:
この質問は延々と審議される可能性がありますが、基本は次のとおりです。
MyOtherClassがポインターでない場合:
MyOtherClassがポインターの場合:
NULL
になる場合があります。これは、コンテキストで意味があり、メモリを節約できます。私は次のルールに従います:メンバーオブジェクトがカプセル化オブジェクトと共に存続し、死ぬ場合は、ポインターを使用しないでください。メンバーオブジェクトが何らかの理由でカプセル化オブジェクトよりも長く存続する必要がある場合は、ポインターが必要になります。手元のタスクに依存します。
通常、メンバーオブジェクトが与えられ、作成されていない場合は、ポインターを使用します。そうすれば、通常はそれを破壊する必要もありません。
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は、それから派生する他のクラスを指すように柔軟に指定できるため、ポインターメンバーとして保持することをお勧めします。基本的に、動的ポリモーフィズムの実装を支援します。
場合によります... :-)
ポインタを使用してclass A
、タイプAのオブジェクトを作成する必要があります。クラスのコンストラクター
m_pA = new A();
さらに、デストラクタでオブジェクトを破棄することを忘れないでください。そうしないと、メモリリークが発生します。
delete m_pA;
m_pA = NULL;
代わりに、タイプAのオブジェクトをクラスに集約する方が簡単です。オブジェクトの存続期間の終了時に自動的に行われるため、オブジェクトを破棄することを忘れないでください。
一方、ポインタを持つことには、次の利点があります。
オブジェクトがスタックに割り当てられ、タイプAが大量のメモリを使用する場合、これはスタックではなくヒープから割り当てられます。
Aオブジェクトを後で(たとえば、メソッドCreate
で)構築することも、早期に破棄することもできます(メソッドClose
で)
メンバーオブジェクトへの関係をメンバーオブジェクトへの(std :: auto_ptr)ポインターとして維持する親クラスの利点は、オブジェクトのヘッダーファイルを含める必要がなく、オブジェクトを前方宣言できることです。
これにより、ビルド時にクラスが分離され、メンバーオブジェクトの関数にアクセスしない場合でも、親クラスのすべてのクライアントを再コンパイルすることなく、メンバーオブジェクトのヘッダークラスを変更できます。
Auto_ptrを使用する場合は、通常は初期化子リストで行うことができる構成を処理するだけで済みます。破壊は親オブジェクトとともにauto_ptrによって保証されます。
簡単なことは、メンバーをオブジェクトとして宣言することです。この方法では、コピーの構築、破棄、割り当てを気にする必要はありません。これはすべて自動的に処理されます。
ただし、ポインタが必要な場合もあります。結局のところ、マネージ言語(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の他の場所には多くのリソースがあります)。