コンストラクタで例外がスローされた場合、デストラクタは呼び出されないことを知っています(単純なクラス、継承なし)。そのため、コンストラクターで例外がスローされた場合、一部のヒープ メモリがクリーンアップされない可能性があります。では、ここでのベストプラクティスは何ですか? コンストラクターで関数を呼び出す必要があり、例外がスローされる可能性があると仮定しましょう。この場合、常に共有ポインタを使用する必要がありますか? 代替手段は何ですか?ありがとうございました!
4 に答える
非常に頻繁にできることは、コンストラクターの前に失敗する可能性のある関数を呼び出し、失敗する可能性のある関数が返す値でインストラクターを呼び出すことです。
#include <string>
#include <iostream>
#include <memory>
class Object {};
これはObject
、私たちのクラスが必要とするほんの一部です。接続されたソケットまたはバインドされたソケットの可能性があります。コンストラクター内で接続またはバインドしようとすると失敗する可能性があるもの。
Object only_odd( int value ) {
if ( value % 2 == 0 )
throw "Please use a std::exception derived exception here";
else
return Object();
}
この関数はオブジェクトを返し、失敗するとスローします (偶数ごとに)。したがって、これはデストラクタで最初にやりたいことかもしれません。
class ugly {
public:
ugly ( int i ) {
obj = new Object;
try{
*obj = only_odd( i );
}
catch ( ...) {
delete obj;
throw ( "this is why this is ugly" );
}
}
~ugly(){ delete obj; }
private:
Object* obj;
};
better
失敗してスローされる可能性のある事前に構築された値を取ります。better
したがって、すでに初期化されているオブジェクトからクラスを構築することもできます。次に、クラスが構築される前でもエラー処理を行うことができ、コンストラクターからスローする必要はありません。さらに良いことに、メモリ処理にスマート ポインターを使用しているため、メモリが確実に削除されるようになっています。
class better {
public:
better ( const Object& org ) : obj { std::make_shared<Object>(org) }
{
}
private:
/*Shared pointer will take care of destruction.*/
std::shared_ptr<Object> obj;
};
これが私たちがそれを使用する方法かもしれません。
int main ( ) {
ugly (1);
/*if only odd where to fail it would fail allready here*/
Object obj = only_odd(3);
better b(obj);
try { /*will fail since 4 is even.*/
ugly ( 4 );
}
catch ( const char* error ) {
std::cout << error << std::endl;
}
}
リソースを処理する必要があり、ユース ケースが標準ライブラリのどのユーティリティでも処理されない場合、ルールは単純です。1 つのリソースのみを処理します。2 つのリソースを処理する必要があるクラスは、それ自体を処理できる 2 つのオブジェクト (つまり、RAII に従うオブジェクト) を格納する必要があります。してはいけないことの簡単な例として、int の動的配列と double の動的配列を必要とするクラスを書きたいとしましょう (ここでは、標準ライブラリのことは忘れてください)。あなたがしないことはこれです:
class Dingbat
{
public:
Dingbat(int s1, int s2)
{
size1 = s1;
size2 = s2;
a1 = new int[s1];
a2 = new int[s2];
}
...
private:
int * a1;
double * a2;
int size1, size2;
};
上記のコンストラクターの問題は、 の割り当てがa2
失敗した場合に例外がスローされ、 のメモリa1
が解放されないことです。もちろん、これは try catch ブロックで処理できますが、複数のリソースがある場合は (不必要に) はるかに複雑になります。
代わりに、単一の動的配列を適切に処理するクラス (またはこの場合は単一のクラス テンプレート) を作成し、自身の初期化、自身のコピー、および自身の破棄を行う必要があります。への呼び出しが 1 つしかnew
ない場合は、割り当てが失敗しても心配する必要はありません。例外がスローされ、メモリを解放する必要はありません。(とにかくそれを処理し、より有益にするために独自のカスタム例外をスローしたい場合があります)
その/それらのクラスがすべて終了したら、Dingbat
クラスにはこれらの各オブジェクトが含まれます。その場合、Dingbat
クラスははるかに単純になり、初期化、コピー、または破棄を処理するための特別なルーチンはおそらく必要ありません。
上記の状況はすでに によって処理されているため、この例はもちろん仮説std::vector
です。しかし、私が言ったように、これは、標準ライブラリでカバーされていない状況が発生した場合のためのものです.