以下のコードはC ++でメモリリークを引き起こしますか
-
02-07-2019 - |
質問
class someclass {};
class base
{
int a;
int *pint;
someclass objsomeclass;
someclass* psomeclass;
public:
base()
{
objsomeclass = someclass();
psomeclass = new someclass();
pint = new int();
throw "constructor failed";
a = 43;
}
}
int main()
{
base temp();
}
上記のコードでは、コンストラクターがスローします。どのオブジェクトがリークされ、メモリリークをどのように回避できますか?
int main()
{
base *temp = new base();
}
上記のコードはどうですか?コンストラクターがスローした後、どのようにメモリリークを回避できますか?
解決
はい、メモリリークが発生します。コンストラクターがスローするとき、デストラクタは呼び出されません(この場合、動的に割り当てられたオブジェクトを解放するデストラクタを表示しませんが、持っていると仮定します)。
これは、スマートポインターを使用する主な理由です。スマートポアトナーは本格的なオブジェクトであるため、例外のスタックのアンワインド中に呼び出されるデストラクタを取得し、メモリを解放する機会があります。
Boostのscoped_ptr <!> lt; <!> gt;のようなものを使用する場合テンプレートの場合、クラスは次のようになります。
class base{
int a;
scoped_ptr<int> pint;
someclass objsomeclass;
scoped_ptr<someclass> psomeclass;
base() :
pint( new int),
objsomeclass( someclass()),
psomeclass( new someclass())
{
throw "constructor failed";
a = 43;
}
}
また、メモリリークは発生しません(デフォルトのdtorは動的メモリ割り当てもクリーンアップします)。
要約すると(そして、できれば、これは、
base* temp = new base();
statement):
コンストラクター内で例外がスローされた場合、オブジェクトの構築の中止で発生した可能性のあるリソース割り当てを適切に処理することに関して注意すべきいくつかの点があります:
- 構築中のオブジェクトのデストラクタは、呼び出されません。
- そのオブジェクトのクラスに含まれるメンバーオブジェクトのデストラクタが呼び出されます
- 構築されていたオブジェクトのメモリが解放されます。
これは、オブジェクトがリソースを所有している場合、コンストラクターがスローしたときにすでに取得されている可能性のあるリソースをクリーンアップするために使用できる2つのメソッドがあることを意味します。
- 例外をキャッチし、リソースを解放してから再スローします。これは修正が難しく、メンテナンスの問題になる可能性があります。
- オブジェクトを使用してリソースの有効期間(RAII)を管理し、それらのオブジェクトをメンバーとして使用します。オブジェクトのコンストラクターが例外をスローすると、メンバーオブジェクトはdesctructorsが呼び出され、ライフタイムが責任を持つリソースを解放する機会があります。
他のヒント
両方の新しいものがリークされます。
ヒープが作成されたオブジェクトのアドレスを名前付きスマートポインターに割り当てて、例外がスローされたときに呼び出されるスマートポインターデストラクタ内で削除されるようにします-( RAII )。
class base {
int a;
boost::shared_ptr<int> pint;
someclass objsomeclass;
boost::shared_ptr<someclass> psomeclass;
base() :
objsomeclass( someclass() ),
boost::shared_ptr<someclass> psomeclass( new someclass() ),
boost::shared_ptr<int> pint( new int() )
{
throw "constructor failed";
a = 43;
}
};
Now psomeclass <!> amp; pint デストラクターは、コンストラクターで例外がスローされたときにスタックがアンワインドされるときに呼び出され、それらのデストラクターは割り当てられたメモリの割り当てを解除します。
int main(){
base *temp = new base();
}
(non-plcaement)newを使用した通常のメモリ割り当てでは、コンストラクタnewが例外をスローすると、演算子newによって割り当てられたメモリが自動的に解放されます。 (Mike Bの回答へのコメントに応じて)個々のメンバーを解放する理由に関して、自動解放は、他の場合ではなく、新しく割り当てられているオブジェクトのコンストラクターで例外がスローされた場合にのみ適用されます。また、解放されるメモリはオブジェクトメンバに割り当てられたメモリであり、コンストラクタ内などで割り当てたメモリではありません。つまり、メンバー変数 a 、 pint 、 objsomeclass 、および psomeclass のメモリを解放しますが、 new someclass()および new int()から割り当てられたメモリ。
一番上の答えは間違っていると思いますが、それでもメモリがリークするでしょう。 クラスメンバのデストラクタは、コンストラクタが例外をスローした場合は呼び出されません(初期化が完了せず、おそらく一部のメンバがコンストラクタ呼び出しに到達していないため)。 それらのデストラクタは、クラスのデストラクタ呼び出し中にのみ呼び出されます。それは理にかなっています。
この単純なプログラムはそれを示します。
#include <stdio.h>
class A
{
int x;
public:
A(int x) : x(x) { printf("A constructor [%d]\n", x); }
~A() { printf("A destructor [%d]\n", x); }
};
class B
{
A a1;
A a2;
public:
B()
: a1(3),
a2(5)
{
printf("B constructor\n");
throw "failed";
}
~B() { printf("B destructor\n"); }
};
int main()
{
B b;
return 0;
}
次の出力(g ++ 4.5.2を使用):
A constructor [3]
A constructor [5]
B constructor
terminate called after throwing an instance of 'char const*'
Aborted
コンストラクタが途中で失敗した場合、それを処理するのはあなたの責任です。さらに悪いことに、例外は基本クラスのコンストラクタからスローされる可能性があります! これらのケースに対処する方法は、<!> quot; function try block <!> quot;を使用することです。 (しかし、それでも、部分的に初期化されたオブジェクトの破壊を注意深くコーディングする必要があります。)
問題に対する正しいアプローチは次のようになります。
#include <stdio.h>
class A
{
int x;
public:
A(int x) : x(x) { printf("A constructor [%d]\n", x); }
~A() { printf("A destructor [%d]\n", x); }
};
class B
{
A * a1;
A * a2;
public:
B()
try // <--- Notice this change
: a1(NULL),
a2(NULL)
{
printf("B constructor\n");
a1 = new A(3);
throw "fail";
a2 = new A(5);
}
catch ( ... ) { // <--- Notice this change
printf("B Cleanup\n");
delete a2; // It's ok if it's NULL.
delete a1; // It's ok if it's NULL.
}
~B() { printf("B destructor\n"); }
};
int main()
{
B b;
return 0;
}
実行すると、割り当てられたオブジェクトのみが破棄されて解放される期待される出力が得られます。
B constructor
A constructor [3]
B Cleanup
A destructor [3]
terminate called after throwing an instance of 'char const*'
Aborted
必要に応じて、追加のコピーを使用して、スマート共有ポインターを使用して解決できます。これに似たコンストラクタを書く:
class C
{
std::shared_ptr<someclass> a1;
std::shared_ptr<someclass> a2;
public:
C()
{
std::shared_ptr<someclass> new_a1(new someclass());
std::shared_ptr<someclass> new_a2(new someclass());
// You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack.
a1 = new_a1;
a2 = new_a2;
}
}
頑張って、 Tzvi。
コンストラクタをスローする場合、throwの呼び出しの前に来たすべてのものをクリーンアップする必要があります。継承を使用している場合、またはデストラクターをスローしている場合は、実際にそうすべきではありません。動作は奇妙です(私の標準は便利ではありませんが、未定義かもしれません)。
はい、そのコードはメモリをリークします。 <!> quot; new <!> quot;を使用して割り当てられたメモリブロック例外が発生しても解放されません。これは、 RAII の背後にある動機の一部です。
メモリリークを回避するには、次のようにします。
psomeclass = NULL;
pint = NULL;
/* So on for any pointers you allocate */
try {
objsomeclass = someclass();
psomeclass = new someclass();
pint = new int();
throw "constructor failed";
a = 43;
}
catch (...)
{
delete psomeclass;
delete pint;
throw;
}
すべての<!> quot; new <!> quot;削除する必要があります。そうしないと、メモリリークが発生します。したがって、次の2行:
psomeclass = new someclass();
pint = new int();
実行する必要があるため、メモリリークが発生します。
delete pint;
delete psomeclass;
finallyブロックで、それらがリークされないようにします。
また、この行:
base temp = base();
は不要です。あなたがする必要があるのは:
base temp;
<!> quot; = base()<!> quot;の追加不要です。
psomeclassを削除する必要があります...整数をクリーンアップする必要はありません...