今さらですが、スマートポインタ使用時のコンストラクタでの例外処理についてよくわかっていないかったので実験してみました。スマートポインタについては非常に詳しい説明がすでにあるので、ここではしません。C++11を想定しています。
コンストラクタでの例外処理
コンストラクタ内で複数のリソースを動的に確保し、かつ、その際に例外が発生する可能性がある場合。例えば下のようなケース。
#include <iostream>
#include <string>
class Hoge
{
std::string name;
public:
Hoge(std::string name, bool irregular) : name(name)
{
std::cout << "Constructor, name = " << name << std::endl;
if(irregular)
{
std::cout << "An exception will be thrown..." << std::endl;
throw 1;
}
}
~Hoge()
{
std::cout << "Destructor, name = " << name << std::endl;
}
};
class HogeHoge
{
private:
Hoge *hoge;
Hoge *fuga;
public:
HogeHoge()
: hoge(nullptr), fuga(nullptr)
{
hoge = new Hoge("Hoge", false);
fuga = new Hoge("Fuga", true); //例外が発生する
}
~HogeHoge()
{
delete hoge;
delete fuga;
}
};
int main(int argc, char* argv[])
{
try
{
HogeHoge hoge;
}
catch(int e)
{
std::cout << "An exception is caught."<< std::endl;
}
return 0;
}
実行結果は次の通りです。
Constructor, name = Hoge
Constructor, name = Fuga
An exception will be thrown...
An exception is caught.
__コンストラクタで例外が発生するとデストラクタが呼び出されません。__なので、デストラクタに解放処理を書いていたとしても、例外が発生する前に確保したリソースが解放されません。
正しく処理しようとすると、Hogeクラスは下のようになるでしょうか?
class HogeHoge
{
private:
Hoge *hoge;
Hoge *fuga;
public:
HogeHoge()
: hoge(nullptr), fuga(nullptr)
{
try
{
hoge = new Hoge("Hoge", false);
fuga = new Hoge("Fuga", true);
}
catch(...)
{
delete hoge;
throw;
}
}
~HogeHoge()
{
delete hoge;
delete fuga;
}
};
実行結果は次の通り。
Constructor, name = Hoge
Constructor, name = Fuga
An exception will be thrown...
Destructor, name = Hoge
An exception is caught.
確かに正しく解放できています。しかし、デストラクタと同じような処理をコンストラクタに入れたり、例外を再度投げたりと、ムダが多く間違えを起こしやすそうなコードです。
スマートポインタを使う
#include <memory>
class HogeHoge
{
private:
std::unique_ptr<Hoge> hoge;
std::unique_ptr<Hoge> fuga;
public:
HogeHoge()
{
hoge = std::unique_ptr<Hoge>(new Hoge("Hoge", false));
fuga = std::unique_ptr<Hoge>(new Hoge("Fuga", true));
}
};
実行結果は次の通り。
Constructor, name = Hoge
Constructor, name = Fuga
An exception will be thrown...
Destructor, name = Hoge
An exception is caught.
このように例外発生前に確保していたhogeに関するリソースが自動で解放されています。生のポインタを扱うよりシンプルです。
できるだけ生のポインタは使わない方がよい
このようにスマートポインタを使うと、リソース管理で間違いが起こりづらいので、生のポインタはできるだけスマートポインタで隠蔽すべきなのだと実感しました。