0

重複の可能性:
3 つのルールとは?

デストラクタが必要な場合は、実際にはオーバーロードが必要だと人々は言いますoperator=

struct longlife{ };
class z
{
 z(){};
 ~z(){ for( auto it=hold.begin();it!=hold.end() ++it ) delete(*it); };
 vector<longlife*> hold;
};

挿入されたすべてのポインターholdnewヒープに割り当てられたと仮定すると、なぜこの例でデコンストラクター以外に何かが必要なのでしょうか?

つまりanything else

z& operator=( const z&ref )
{
 hold = ref.hold;
 return *this;
}

だろう:

z a;
a.hold.push_back( heap_item );
z a2;
a2 = a;

メモリリークの原因?3 のルールがルールである理由を理解するのが難しい場合があります

4

5 に答える 5

3

代入演算子が必要なだけでなく、コピー コンストラクターも実装する必要があります。longlifeそれ以外の場合、コンパイラは、同じインスタンスへのポインターを含む両方のコピー (割り当て/コピー構築後) になるデフォルトの実装を提供します。両方のコピーのデストラクタは、deleteこれらのインスタンスを未定義の動作に導きます。

z a;
a.hold.push_back( heap_item );
z a2;
a2 = a;

と の両方a.hold[0]a2.hold[0]、同じ へのポインタが含まれていますheap_item。したがって、破壊中に二重削除が発生します。

代入演算子とコピー コンストラクターを実装する必要を回避する簡単な方法は、スマート ポインターを使用してlonglifeインスタンスを に保持することvectorです。

std::vector<std::unique_ptr<longlife>> hold;

クラスのデストラクタを記述する必要さえなくなりました。


C++03 の場合、オプションはの代わりにstd::tr1::shared_ptr(またはboost::shared_ptr)をunique_ptr使用するか、 の代わりにboost::ptr_vector(もちろん、これは C++11 のオプションでもあります) を使用することですstd::vector

于 2012-10-10T20:53:42.303 に答える
2

代入演算子とコピー コンストラクターがないと、複数のholdベクトルが同じヒープ項目を指すことになり、破棄時に未定義の動作が発生する可能性があるためです。

z firstZ;
if (somethingIsTrue) {
    z otherZ = firstZ;
    // play with otherZ...
    // now otherZ gets destructed, along with longlife's of the firstZ
}
// now it's time to destroy the firstZ, but its longlife's are long gone!

もちろん、「普通の」ポインタのベクトルではなく、オブジェクトのベクトルまたは「スマート ポインタ」のベクトルを使用していれば、この問題は発生しません。

詳細については、3 つのルールを参照してください。

于 2012-10-10T20:52:08.250 に答える
1

実際には、メモリ リークではなく、二重の解放が行われます。

STL コンテナーは、参照ではなくオブジェクトを格納します。あなたの場合objectはポインタです。ポインタは単純にコピーされます。あなたの行a2 = a;は、ベクトル内のポインターを複製します。その後、各デストラクタはポインタを解放します。

二重解放は、メモリ リークよりもはるかに危険です。それは厄介な未定義の動作を引き起こします:

MyStruct *p1 = new MyStruct();
delete p1;
.... do something, wait, etc.
delete p1;

同時に他のスレッドで:

MyOptherStruct *p2 = new MyOtherStruct();
.... do something, wait, etc.
p2->function();

を最初に呼び出した後は解放p2されるため、メモリ アロケータが に使用された値とまったく同じ値を割り当てることが判明する場合があります。アロケータは、これが. 問題は でのみ発生します。スレッド 2 のコードを見ると、何がうまくいかなかったのか、その理由を理解することは絶対に不可能です。これは、特にシステムが大きい場合、デバッグが非常に困難です。p1delete p1delete p1p2p2->function();

于 2012-10-10T20:51:29.137 に答える
1

aorのデストラクタ (2 番目に破棄された方) で二重の削除 (およびクラッシュ) が発生しa2ます。これは、デフォルトの代入コンストラクタが のメモリ状態のバイナリ コピーを実行するためですhold。したがって、各オブジェクトaa2まったく同じメモリが削除されることになります。

于 2012-10-10T20:52:54.900 に答える
1

あなたのコメントから:

@Xeo、私は3のルールが何であるかを理解しています。問題は主になぜそれがルールなのかです

ここで何が起こるか考えてみましょう:

z& operator=( const z&ref )
{
 hold = ref.hold;
 return *this;
}

のインスタンスがあるとしましょうz:

z myz;
myz.a.hold.push_back( new long_life );

...そして、これのコピーを作成しますmyz:

z my_other_z;
// ...
my_other_z = myz;

上記operator=で提供した実装は、vector. にポインタがある場合、vector指されているもののコピーは作成されません。ポインタ自体のリテラル コピーが作成されるだけです。

したがって、operator=リターン後z、同じものを指すポインタを持つ の 2 つのインスタンスができます。これらの最初のzs が破棄されると、ポインターが削除されます。

~z(){ for( auto it=hold.begin();it!=hold.end() ++it ) delete(*it); };

z2 番目が破棄される時が来るとdelete、同じポインターをもう一度試みます。これにより、未定義の動作が発生します。

この問題の解決策は、割り当てと削除が必要なリソースを維持するオブジェクトを割り当てまたはコピーするときに、ディープ コピーを作成することです。これは、代入演算子とコピー コンストラクターを提供することを意味します。

そのため、3 のルールは RULE です。

編集:

他の人が述べたように、値セマンティクスと RAII を使用することで、これを完全に回避することができます。他の人がそう呼んでいるように、ゼロのルールを使用するようにオブジェクトを再設計することは、はるかに優れたアプローチです。

于 2012-10-10T21:03:43.653 に答える