C++はヘビーゲーマー向けだと思う

C++ は基本的に嫌いじゃない言語なんですが、時々どうしようもなく C++ で仕事をするのを辞めたくなることがあります。

実は先日、プロジェクトで謎のメモリリークが発生しました。私のプロジェクトでは、メモリ管理はスマートポインタに任せていますので、こちらにプロファイラを仕込んで観測してみたところ、こんな感じのコードが原因でした。

class InterfaceFoo
{
public:
  virtual Bar getBar() = 0;
};

class ConcreteFoo : public InterfaceFoo
{
  shared_ptr< Bar > m_bar;;
public:
  Bar getBar(){ return *m_bar; }
};

int main( void )
{
  shared_ptr< InterfaceFoo > foo = new ConcreteFoo;

  // 何か処理

  return 0;
}

見ての通り、InterfaceFoo に 仮想デストラクタが定義されてません。だからコンパイラが作るデフォルトのデストラクタは非virtualになります。一方で、ConcreteFoo で作られる合成デストラクタからメンバ m_bar のデストラクタが呼ばれshared_ptr の参照カウンタ減算が行われます。

で、InterfaceFoo foo でデストラクタが呼ばれると、これはバーチャルじゃないので、実際の値の型である ConcreteFoo のデストラクタが呼ばれません。で、参照カウンタがへらなくってメモリがリークすると言う...。Effective C++ の頭の方に乗っている、定番中の定番の不具合です。うかつ。穴があったら入りたいです。

実は gcc の警告でそれっぽいのがあるので、すっかり安心しきっていたのですが、それは virtual のメンバを持っていて、空でない非virtualデストラクタ(コンパイラが作るものも含む)がある場合にだけ警告を出している模様。Interface 的な 抽象クラスでは全く警告がでませんにゃ。うみゅー。*1

こういう不具合に出会うとつい思ってしまうのです。「安全側に倒せばいいじゃん! virtual のメソッドがあったら デフォルトデストラクタも virtual にしてよ!」って。もちろんそれが「使わないものにコストを払わない」という C++ のポリシーに反するからやっていないのは知っているのですが、それでも思ってしまいます。

C++マニュアル車なのはいいのです。でもクラッチにミスるとエンジンが止まるんじゃ無くって、クラッチにミスるとブレーキがきかなくなるデザインってどうよ。


* * *


C++ を好きになる気持ちは、ゲーマー心理に似ていると思います。理不尽な難易度のゲームを楽しめる、努力とやりこみでねじ伏せる。なんか C++ って末期のシューティングゲームのようです。落とし穴や即死トラップ有りすぎ(T△T

また、作成者の意図しないバグ技で戦略の幅が広がり信じられないようなゲーム性の広がりを見せちゃったりする やりこみがいがある様も、C++ にピッタリ填ります。

追記 2007-11-11:

コメントで指摘いただいたとおり、boost::shared_ptr では、仮想デストラクタが定義されていなくても上記のような不具合が生じません(上の話は手前実装 shared_ptr での不具合でした。ちゃんと書かないですみません)。boost::shared_ptr の delete については、紹介いただいたこちらに詳しい解説があります。(mb2syncさん、う さん ありがとうございます)

ふみ、dynamic_deleter ですか。凄いなぁ。「そのバグはもう死んでいる」って感じです。boost のある C++ と 無い C++ って、もはや別の言語ですよねぇ。やりこみゲーマーハッカー の凄まじさを感じます。普通に接する分にはツンツンだけど、知り尽くして没入すればデレデレ。きっとここら辺が C++ の 萌ゆるポイントなのでしょうねぇ。まだまだだ、わたし。

*1:結局 インターフェイス命名規約なクラスにvirtualデストラクタが定義されていないと警告するPython スクリプトを書いて再発防止に(穴があるけどないよりマシということで)。他にいい方法ないかなぁ。