C++でデストラクタを呼ばなくていい条件

参考: デストラクタを呼ばずに再構築 - melpon日記 - HaskellもC++もまともに扱えないへたれのページ


C++ では、動的に確保したメモリ領域にオブジェクトを構築した場合、
確保した領域を解放する前に、デストラクタを呼び出す必要があります:

// 何らかの理由で new 以外の方法でメモリ確保する必要がある場合
// メモリ確保し、オブジェクトを構築する(todo:例外安全)
void* const vp = allocate_memory( sizeof(T) );
T* const p = ::new(vp) T();

// 何らかの処理

// デストラクタ呼び出し
p->~T();
// 解放
free_memory( vp );


普通に C++ を使うだけであれば、動的メモリ確保は new/delete を使えばいいし、
そもそも unique_ptr とか shared_ptr とかで管理するのが普通なので、
このあたりのことは特に意識する必要はありません(し、意識しないようなコードを書くべきです)。


しかしながら、時々、意識せざるを得ない場合があります。
僕の場合は、 Lua でユーザデータを作ろうと思ったときに、この問題にぶち当たりました。
Lua のユーザデータは、メモリの解放処理が LuaGC によって行われるので、
C++ 側では解放のタイミングを掴むことが出来ません。

void* const vp = lua_newuserdata( L, sizeof(T) );
T* const p = ::new(vp) T();

// vp は Lua の GC によって管理される
// いつ領域が解放されるかは分からない!

この問題は、メタテーブルの __gc にデストラクタを登録することで対処できますが、
処理速度的に、出来る事ならばメタテーブルは使いたくない、というのは自然だと思います。
少なくとも、 C でユーザデータを使う場合には、特別な破棄処理なんて必要ないので、
ゼロオーバーヘッドを原則としている C++ で、それが出来無い道理はない筈です。


そう思って調べたところ、やはりというか、
C++ でもデストラクタ呼び出しが必須でない場合はあるようです。
その条件は、上に挙げた めるぽん先生の日記によれば、

  • trivial なデストラクタを持つ
  • 副作用のない non-trivial なデストラクタを持つ

このどちらかであれば、明示的にデストラクタを呼ぶことなく、
勝手に領域の再利用/解放を行っていい、とのこと(規格は N3092 の 3.8 を参照)。


で、この条件について、後者の「副作用のない non-trivial なデストラクタを持つ」というのは判定が難しいですが、
前者の「 trivial なデストラクタを持つ」というのに関しては、
boost/tr1 の type_traits に has_trivial_destructor というメタ関数が存在します。


後者に関しては、前者に比べて数は遥かに少ないので、特に気にする必要もないですが、
もしどうしても最適化したいのであれば、自前で

template<typename T>
struct has_nosideeffect_destructor : boost::has_trivial_destructor<T> {};

struct hoge
{
  ~hoge() throw() {}
};
template<>
struct has_nosideeffect_destructor<hoge> : boost::true_type {};

のようなメタ関数を作れば良いと思います。
コンパイラ側で用意してくれればベストなのですが、流石に難しいでしょうし。


最後に、一応念押しというか、お約束というか。
C++ において、メモリ領域の管理は、基本的に手動で行うべきではありません。
動的メモリ領域であれば shared_ptr や unique_ptr を、
スタックやオブジェクト中のメモリ領域であれば boost::optional や boost::variant を、
それぞれ使うべきです。それらのライブラリでは、安全性だけでなく効率も考慮されています。
上に挙げた placement new やデストラクタ呼び出しは、あくまで例であり、
普段 C++ で書くにあたって、そのようなコードを書く必要は一切ありません。
もし万一、何らかの理由が有って、手動でメモリ領域を管理しなければならない場合は、
とにかく慎重に、規格とにらめっこしながらコードを書くようにした方が良いです。
そして、常に「より良い代替」は無いか、を模索するようにしたいですね。

追記( 09/26, 2011 )

Boost や TR1 の has_trivial_destructor は, C++11 では is_trivially_destructible という名前に変化しています.
とはいえ,これは割と最近の変更なので,多くのコンパイラでは未だに has_trivial_destructor という名前のままです.
ちなみに機能は全く変わりません. ヘッダも のままです.