Singletonパターンを色々検証してみた
Singletonクラスに依存関係のあるメンバを置く際にどうやればいいのか色々検証してみました
C++でSingletonパターンを書く時の基本のコード
class Singleton { private: static Singleton *me; Singleton() { std::cout << "Singleton()" << std::endl; } ~Singleton() { std::cout << "~Singleton()" << std::endl; } public: static Singleton *GetInstance() { if (me == 0) me = new Singleton(); return me; } static void DeleteInstance() { if (me != 0) delete me; } int x; void hoge() { std::cout << "hoge()" << std::endl; } }; Singleton *Singleton::me = 0; int main() { Singleton::GetInstance()->x = 3; std::cout << "x:" << Singleton::GetInstance()->x << std::endl; Singleton::GetInstance()->hoge(); Singleton::DeleteInstance(); std::cout << "program end" << std::endl; return 0; }
実行結果
Singleton() x:3 hoge() ~Singleton() program end
このやり方はプログラムの終了前にdeleteされてますので依存関係のあるメンバを置くことが出来ます、
しかし明示的に開放してあげないといけないため、C++の流儀に合っていませんし、生ポインタもむき出しです。
スマートポインタを使って自動的に開放してみる
まずはboost::shared_ptrを使った実装
class Singleton { private: static boost::shared_ptr<Singleton> me; Singleton() { std::cout << "Singleton()" << std::endl; } ~Singleton() { std::cout << "~Singleton()" << std::endl; } public: static boost::shared_ptr<Singleton> &GetInstance() { if (me.get() == 0) me.reset(new Singleton(), [](Singleton *p) { delete p; } ); return me; } int x; void hoge() { std::cout << "hoge()" << std::endl; } }; boost::shared_ptr<Singleton> Singleton::me; int main() { Singleton::GetInstance()->x = 3; std::cout << "x:" << Singleton::GetInstance()->x << std::endl; Singleton::GetInstance()->hoge(); std::cout << "program end" << std::endl; return 0; }
実行結果
Singleton() x:3 hoge() program end ~Singleton()
ラムダ式がクロージャの役割を果たしてくれてますのでprivateメンバのmeにもアクセス出来ています
ただboost::shared_ptrは参照カウントなので、Singletonなクラスにはstd::unique_ptrやboost::interprocess::unique_ptrを使った方がスマートかと思います
って事でboost::interprocess::unique_ptrの実装
class Singleton { private: struct SingletonDeleter { void operator()(Singleton *p) { delete p; } }; typedef boost::interprocess::unique_ptr<Singleton, SingletonDeleter> SingletonPtr; static SingletonPtr me; Singleton() { std::cout << "Singleton()" << std::endl; } ~Singleton() { std::cout << "~Singleton()" << std::endl; } public: static SingletonPtr &GetInstance() { if (me.get() == 0) me.reset(new Singleton()); return me; } int x; void hoge() { std::cout << "hoge()" << std::endl; } }; Singleton::SingletonPtr Singleton::me; int main() { // boost::shared_ptrの時と同じ }
実行結果
boost::shared_ptrの時と同じ
この二つのやり方は自動的にdeleteをしてくれているものの、グローバル領域の為開放順序が定まっておらず、
依存関係のあるクラスメンバがあるとdeleteの途中でエラーが発生したりする場合があります、
ただし大抵の本やサイトではこのような書き方をしてSingletonに依存関係のあるメンバは持たすな、という意見で一致してるように思います。
って事で解決法
色々検証した結果、したのような書き方になりました
class Singleton { private: struct SingletonDeleter { void operator()(Singleton *p) { delete p; } }; typedef boost::interprocess::unique_ptr<Singleton, SingletonDeleter> SingletonPtr; static SingletonPtr me; Singleton() { std::cout << "Singleton()" << std::endl; } ~Singleton() { std::cout << "~Singleton()" << std::endl; } public: static SingletonPtr &GetInstance() { if (me.get() == 0) me.reset(new Singleton()); return me; } static void DeleteInstance() { if (me.get() != 0) me.reset(); } int x; void hoge() { std::cout << "hoge()" << std::endl; } }; Singleton::SingletonPtr Singleton::me; int main() { { struct Caller { void operator()(int *p) { delete p; Singleton::DeleteInstance(); } }; boost::interprocess::unique_ptr<int, Caller> caller(new int(-1)); // int型はdummy Singleton::GetInstance()->x = 3; std::cout << "x:" << Singleton::GetInstance()->x << std::endl; Singleton::GetInstance()->hoge(); } std::cout << "program end" << std::endl; return 0; }
実行結果
Singleton() x:3 hoge() ~Singleton() program end
スマートポインタをプログラム終了前のコールバック関数として使うってやり方です、
少し変な書き方なので突っ込み待ち