Singleton の暗部。
幸運な人(?)は問題に遭遇しないかも知れない。
foo.h
#pragma once #include <iostream> #include "singleton.h" class Foo final : public Singleton<Foo> { friend class Singleton<Foo>; private: Foo() { std::cout << __func__ << std::endl; } public: ~Foo() { std::cout << __func__ << std::endl; } };
bar.h
#pragma once #include <iostream> #include "singleton.h" class Bar : public example::Singleton<Bar> { friend class example::Singleton<Bar>; private: Bar() { std::cout << __func__ << std::endl; } public: ~Bar() { std::cout << __func__ << std::endl; } };
main.cpp
#include "foo.h" #include "bar.h" void main() { Bar::GetMutableInstance(); Foo::GetMutableInstance(); }
水面下(プログラム終了処理)で起きていること。
Bar::Bar()
Foo::Foo()
Bar::~Bar()
Foo::~Foo()
Singleton が他の Singleton に依存していると破綻する。
Foo::~Foo() で Bar を参照している場合など。
というわけで終了時の処理を手法はどうあれ制御可能にする必要がある。
singleton_finalizer.h
class SingletonFinalizer final { template<typename T> friend class Singleton; public: using Callback = std::function<void()>; public: SingletonFinalizer() = delete; ~SingletonFinalizer() = delete; SingletonFinalizer(const SingletonFinalizer&) = delete; SingletonFinalizer& operator=(const SingletonFinalizer&) = delete; SingletonFinalizer(SingletonFinalizer&&) = delete; SingletonFinalizer& operator=(SingletonFinalizer&&) = delete; private: static void Register(const Callback& cb); static void Finalize(); };
singleton_finalizer.cpp
#include <iostream> #include <mutex> #include "singleton_finalizer.h" static std::stack<SingletonFinalizer::Callback> cbs; static std::once_flag once; void SingletonFinalizer::Register(const Callback& cb) { std::cout << __func__ << std::endl; std::call_once(once, []() { std::cout << __func__ << "atexit" << std::endl; std::atexit(SingletonFinalizer::Finalize); }); cbs.push(cb); } void SingletonFinalizer::Finalize() { std::cout << __func__ << std::endl; while(!cbs.empty()){ auto& cb = cbs.top(); cb(); cbs.pop(); } }
Bar::Bar() → Foo::Foo() → Bar::~Bar() → Foo::~Foo()
を
Bar::Bar() → Foo::Foo() → Foo::~Foo() → Bar::~Bar()
初期化の逆順にする。
singleton.h
template<typename T> template<typename... Arguments> T& Singleton<T>::GetInstance(Arguments&&... args) { std::call_once( GetOnceFlag(), [](Arguments&&... args) { instance.reset(new T(std::forward<Arguments>(args)...)); SingletonFinalizer::Register(std::bind(&Singleton<T>::DestroyInstance)); }, std::forward<Arguments>(args)... ); assert(instance); return *instance; } template<typename T> void Singleton<T>::DestroyInstance() { if(instance) instance.reset(); }
Singleton<T>::GetInstance で初回のみ SingletonFinalizer に破棄関数を登録。
SingletonFinalizer::Register は初回のみ std::atexit に
SingletonFinalizer::Finalize を登録。
これで初期化の逆順に破棄処理が行われる。
GetInstance の呼び出し順さえ注意しておけばいい。
今回は汚い部分を隠蔽するために SingletonFinalizer のメソッドは private にし
肝心の Finalize 処理を std::atexit に丸投げした。
std::atexit で登録された関数は、プログラム終了処理の際、他の static 変数の破棄より前に
呼び出されるので目的に合致したというわけである。
std::atexit 自体は最低 32個は登録できる仕様なので、他のケースで併用する場合は注意が要る。
その場合は std::atexit を利用するのはやめて、任意の場所で SingletonFinalizer::Finalize を
明示的に呼び出す必要がある。