アロケータを指定できて参照がなくなった時点で破棄されるSingleton
はじめに
shared_ptrを使用したSingletonの実装
Singletonのアロケータを後から指定 (shared_ptrを使用したSingletonの実装の続き)
上2つの続き的なものですが読まなくて大丈夫です。
実装
// アロケータ指定用
template <typename T>
struct singleton_allocator
{
template <typename U>
using type = std::allocator<U>;
};
// singleton
template <typename T>
class singleton
{
private:
struct impl : public T{};
using allocator_type = typename singleton_allocator<T>::template type<impl>;
protected:
singleton() = default;
public:
virtual ~singleton() { instance_.reset(); }
singleton( singleton const& ) = delete;
singleton& operator=( singleton const& ) = delete;
public:
static std::shared_ptr<T> get_instance()
{
auto ret_ptr = instance_.lock();
if( ret_ptr )
{
return ret_ptr;
}
ret_ptr = std::allocate_shared<impl>( allocator_ );
instance_ = std::weak_ptr<T>( ret_ptr );
return ret_ptr;
}
private:
static std::weak_ptr<T> instance_;
static allocator_type allocator_;
}; // class singleton
template <typename T> std::weak_ptr<T> singleton<T>::instance_;
template <typename T>
typename singleton<T>::allocator_type singleton<T>::allocator_;
使い方
アロケータを指定しない場合
シングルトンにしたいクラスを singleton<T>
を継承して定義します。
class A : public singleton<A>
{
private:
friend class singleton<A>; // コンストラクタがprivateなのでfriend宣言が必要
A(){}
public:
~A(){}
void func(){}
};
使用例
int main()
{
std::shared_ptr<A> a1 = A::get_instance(); // 初回のget_instance()時にインスタンス生成
std::shared_ptr<A> a2 = A::get_instance(); // a1と同じオブジェクトを指す
a1->func(); // shared_ptrなので普通のポインタのようにメンバにアクセスが可能
} // ここで参照が0になるので自動で破棄される
アロケータを指定する場合
使用するアロケータをmy_allocator
とします。
上のclass A
を定義する前に以下のような定義をします。
class A; // 前方宣言
template <>
struct pyrite::singleton_allocator<A>
{
template <typename T>
using type = my_allocator<T>;
};
後はアロケータを指定しない場合と同じです。
解説
template <typename T>
struct singleton_allocator
{
template <typename U>
using type = std::allocator<U>;
};
はじめのこの部分はアロケータを指定するのに必要な部分です。
singleton_allocator
を部分特殊化することでアロケータを指定します。
デフォルトでは std::allocator
を使用します。
型Tのコンストラクタがprivateで宣言されているはずなので allocate_shared<T>
や make_shared<T>
が使えません。
しかし allocate_shared
を使用したいので型Tを継承した impl
を定義します。
struct impl : public T{};
この辺のことは std::make_shared から private コンストラクタを呼び出す にいろいろ書いてあるので是非。
デストラクタ内で instance_.reset()
しているのは make_shared
などで確保したメモリは弱参照が残っていても開放されないからです。
後は特に変わったことはしていないと思います。
まとめ
Singletonの型を変えずに派生クラスを定義する側でアロケータを指定できます。
Singletonのアロケータを指定したいと思うことがあるのかはわかりませんができないよりできたほうが良いだろうというだけです。
参照が0で get_instance()
を呼び出すとインスタンスが生成されます。
参照が0になった時点で自動で破棄されます。
注意しなければならないのは
- 実際確保する方が
singleton<T>::impl
型である - T型に
final
をつけられない
あたりだと思います。