次のコードフラグメントはリークしますか?そうでない場合、foobar()で構築された2つのオブジェクトはどこで破棄されますか?
class B
{
int* mpI;
public:
B() { mpI = new int; }
~B() { delete mpI; }
};
void foobar()
{
B b;
b = B(); // causes construction
b = B(); // causes construction
}
次のコードフラグメントはリークしますか?そうでない場合、foobar()で構築された2つのオブジェクトはどこで破棄されますか?
class B
{
int* mpI;
public:
B() { mpI = new int; }
~B() { delete mpI; }
};
void foobar()
{
B b;
b = B(); // causes construction
b = B(); // causes construction
}
デフォルトのコピー代入演算子は、メンバーごとのコピーを行います。
だからあなたの場合:
{
B b; // default construction.
b = B(); // temporary is default-contructed, allocating again
// copy-assignment copies b.mpI = temp.mpI
// b's original pointer is lost, memory is leaked.
// temporary is destroyed, calling dtor on temp, which also frees
// b's pointer, since they both pointed to the same place.
// b now has an invalid pointer.
b = B(); // same process as above
// at end of scope, b's dtor is called on a deleted pointer, chaos ensues.
}
詳細については、Effective C++、第 2 版の項目 11 を参照してください。
はい、これは漏れます。追加のメソッドが定義されていないため、コンパイラは自動的に追加のメソッドを提供します。生成されるコードは次と同等です。
B & B::operator=(const B & other)
{
mpI = other.mpI;
return *this;
}
これは、次のことが起こることを意味します。
B b; // b.mpI = heap_object_1
B temp1; // temporary object, temp1.mpI = heap_object_2
b = temp1; // b.mpI = temp1.mpI = heap_object_2; heap_object_1 is leaked;
~temp1(); // delete heap_object_2; b.mpI = temp1.mpI = invalid heap pointer!
B temp2; // temporary object, temp1.mpI = heap_object_3
b = temp1; // b.mpI = temp2.mpI = heap_object_3;
~temp1(); // delete heap_object_3; b.mpI = temp2.mpI = invalid heap pointer!
~b(); // delete b.mpI; but b.mpI is invalid, UNDEFINED BEHAVIOR!
これは明らかに悪いことです。これは、3 つのルールに違反した場合に発生する可能性があります。重要なデストラクタとコピー コンストラクタを定義しました。ただし、コピーの割り当ては定義していません。3 つのルールは、上記のいずれかを定義する場合は、常に 3 つすべてを定義する必要があるということです。
代わりに、次の操作を行います。
class B
{
int* mpI;
public:
B() { mpI = new int; }
B(const B & other){ mpI = new int; *mpi = *(other.mpI); }
~B() { delete mpI; }
B & operator=(const B & other) { *mpI = *(other.mpI); return *this; }
};
void foobar()
{
B b;
b = B(); // causes construction
b = B(); // causes construction
}
3 つのオブジェクトを構築していて、すべてが破壊されます。問題は、デフォルトのコピー代入演算子が浅いコピーを行うことです。つまり、ポインタがコピーされ、複数回削除されます。これにより、未定義の動作が発生します。
これが3 のルールの背後にある理由です。デストラクタはありますが、他の 2 つはありません。コピー コンストラクターとコピー代入演算子を実装する必要があります。どちらもディープ コピーを実行する必要があります。これは、新しい int を割り当て、値をコピーすることを意味します。
B(const B& other) : mpI(new int(*other.mpI)) {
}
B& operator = (const B &other) {
if (this != &other)
{
int *temp = new int(*other.mpI);
delete mpI;
mpI = temp;
}
return *this;
}
最初に投稿したコードの問題を解決するための別のアプローチを提供するために、ポインタをクラス B に保持し、メモリ管理を取り除くことができると思います。次に、カスタム デストラクタは必要ないので、3 の規則に違反しません...
class B
{
int* mpI;
public:
B() {}
B(int* p) { mpI = p; }
~B() {}
};
void foobar()
{
int* pI = new int;
int* pJ = new int;
B b; // causes construction
b = B(pI); // causes construction
b = B(pJ); // causes construction
delete pI;
delete pJ;
}
何度か指摘したように、あなたは 3 つのルールに違反しています。リンクに追加するだけで、スタック オーバーフローに関する素晴らしい議論があります: What is The Rule of Three?