「C++のコンストラクタで例外を投げてはいけない」は迷信
via. http://www.kmonos.net/wlog/83.html#_1255080321
この件に関してはずっと誤解していたので、調べてみた。
昨日の#lowhacksで騒いでいた件のまとめ。
「C-torで例外を投げてはいけない」というのは、確かに迷信のようだ。
むしろ、Exceptional C++の著者Herb Sutter先生やC++ FAQ Liteでは「C-torの失敗」を伝える方法として積極的に使うよう推奨されている。
「C-torが例外を投げてはいけない」の迷信の根拠として、良く言われているのがC-torが失敗すると、D-torが呼ばれないので、リソースリークが起こる、ということだ。
これは正しくて、C-torが失敗した場合、その生成に失敗したクラスのD-torは呼ばれない。
ためしに以下のようなコードを実行すると、
#include <iostream> void* operator new (size_t size) { void* p = ::malloc(size); std::cout << "new size: " << size << ". p = " << p << std::endl; return p; } void operator delete (void* p) { std::cout << "delete " << p << std::endl; ::free(p); } class Res { public: Res() { std::cout << "Res C-tor" << std::endl; } ~Res() { std::cout << "Res D-tor" << std::endl; } }; class Member { public: Member() { std::cout << "Member C-tor" << std::endl; } ~Member() { std::cout << "Member D-tor" << std::endl; } }; class A { public: A() { std::cout << "A C-tor" << std::endl; m_pres = new Res; throw 1; } ~A() { delete m_pres; std::cout << "A D-tor" << std::endl; } private: Res* m_pres; Member m_member; }; int main() { try { A* pa = new A; std::cout << "doesnot come here!" << std::endl; delete pa; } catch(int e) { std::cout << "exception : " << e << " caught" << std::endl; } return 0; }
実行結果はこのようになる。
new size: 8. p = 003A5E40 Member C-tor A C-tor new size: 1. p = 003A5F20 Res C-tor Member D-tor delete 003A5E40 exception : 1 caught
つまり、このコードは以下のような手順で実行されていることがわかる。
- Aメモリ確保
- メンバ変数のC-tor
- AのC-tor
- Resメモリ確保
- ResのC-tor
- メンバ変数のD-tor
- Aメモリ開放
確かに、Aのデストラクタは実行されていない。
AのC-torで確保したm_presはコンストラクタでdeleteされることなく、そのままリークしてしまう。
注:A自身のメモリはリークしていないことに注意。C-torがthrowした場合、自動的にdeleteは呼び出される。delete this;を書かなければならないというのは迷信。
しかし、メンバ変数自身のD-torはきちんと呼び出されていることがわかる。
よって、メンバ変数それぞれに各リソースの責任を持たせスマートポインタ等を用いRAIIパターンを徹底すれば、AのD-torが呼び出されなくても正しくリソースを開放するように書くことができる。
先ほどの例では、Aクラスの実装をスマートポインタstd::auto_ptrを使うように書き換えれば、正しくリソースを開放することができる。
class A { public: A() { std::cout << "A C-tor" << std::endl; m_pres.reset(new Res); throw 1; } ~A() { std::cout << "A D-tor" << std::endl; } private: std::auto_ptr<Res> m_pres; Member m_member; };
実行結果:
new size: 8. p = 003E6088 Member C-tor A C-tor new size: 1. p = 003E6168 Res C-tor Member D-tor Res D-tor delete 003E6168 delete 003E6088 exception : 1 caught
作業中。まだ続くよ!
回線が不安定なので続きはあとで