Pimpl
実装を隠す手法としてPimplはメジャーな手法だと思うが、思わぬ罠が潜んでいてくそはまった。。
最初はARMのコンパイラが駄目なのかと思ったが、VC2005で試したところこちらでも意図しない挙動を見せてビビる。
簡単に説明すると、Pimplの実装クラスと同名のクラスが他のファイルでローカルクラスとしてstaticで静的に作られるとPimplクラスのvtblがぶっ壊れるというもの。
どのくらいやばいかというとPimplのデストラクタが呼ばれる瞬間にヌルポで死ぬ。
デストラクタまで行かないとかやばすぎるだろ。
名前を衝突しないようにすればもちろん大丈夫なのだが、ファイルローカルなクラスでもコンパイラが混乱するとは。
これって結構メジャーなバグなんかな。
他の人が勝手に同名のクラスを作ってるとやばいね。
以下VC2005のテストコード。
pimpl_class.h
#pragma once class base { public: base() {}; virtual ~base() {} virtual void foo() = 0; }; // 純粋仮想クラス. class derived : public base { protected: derived() {}; public: virtual ~derived() {} virtual void foo() = 0; static derived *create(); };
pimpl_class.cpp
#include <stdio.h> #include "pimpl_class.h" // local_classと同じ名前を付ける. class hoge : public derived { public: hoge() { printf("pimpl_class: hoge::ctor\n"); } virtual ~hoge() { printf("pimpl_class: hoge::dtor\n"); } void foo() { printf("pimpl_class: hoge::foo\n"); } }; derived *derived::create() { return new hoge(); }
local_class.cpp
#include <stdio.h> // 別のファイルに同名のPimplのhogeが居る. class hoge { public: int a; int b; hoge() { a = 0; b = 0; printf("local_class.cpp: hoge::ctor\n"); } ~hoge() { printf("local_class.cpp: hoge::dtor\n"); } void foo() { printf("local_class.cpp: hoge::foo\n"); } }; // staticで実体を定義する. //static hoge s_hoge; // こっちだとpimplクラスの実体が壊れる. void call_hoge_foo() { static hoge *s_hoge = new hoge; // こっちはlocalのhogeのctorがおかくなる. s_hoge->foo(); }
main.cpp
#include "pimpl_class.h" void call_hoge_foo(); int _tmain(int argc, _TCHAR* argv[]) { call_hoge_foo(); derived *p = derived::create(); p->foo(); delete p; return 0; }
staticで実体を定義した時の実行結果.
local_class: hoge::ctor local_class: hoge::foo local_class: hoge::ctor <---なぜかlocalのhogeが呼ばれる. <ここでエラーのダイアログ>
singleton的な作り方をした時の実行結果.
pimpl_class: hoge::ctor <---なぜかpimplのhogeが呼ばれる. local_class: hoge::foo <--ちゃんとlocalのfooがよばれている. pimpl_class: hoge::ctor pimpl_class: hoge::foo pimpl_class: hoge::dtor
localの方で全くstaticな実体を作らなければ問題は起こらないが
同名のクラスはあまり好ましくない状況だというのがなんとなくわかると思う。
C#みたいなクラス分割が使えればそもそもこんな問題で悩む必要はないんだけどなぁ。。