LoginSignup
15
13

More than 5 years have passed since last update.

C++11 と unique_ptr でシングルトン

Last updated at Posted at 2014-07-14

概要

C++11 では関数ローカルでの static 変数の初期化処理がスレッドセーフになったそうなので、シングルトンの実装が楽になりました。

しかし、ただ生ポインタを返すだけでは寂しいので、unique_ptr (正確にはその参照)を返すようにします。

仕様

Class::singleton

シングルトン型。const unique_ptr<Class> & の型定義です。
reset などされないように const になっていますが、const_cast や get で取得してからの解放は有効なので注意が必要です。
後述する Class::instance() は、これか auto & で受け取る必要があります。

Class::singleton Class::instance()

後述する create / destroy 使用時はご注意ください。
Class のインスタンスを Class::singleton で返します。
初回に呼んだ際はインスタンス化を行います。
それ以降は有効かどうかは保証されないため、所有判定を行うことが望ましいです。

void Class::create()

非スレッドセーフです。利用する際は排他制御を行ってください。
Class を明示的にインスタンス化します。既にインスタンス化されていれば何もしません。

void Class::destroy()

非スレッドセーフです。利用する際は排他制御を行ってください。
Class のインスタンスを明示的に破棄します。既に破棄されていれば何もしません。

実装

とりあえずマクロで実装しました。

singleton.h
// シングルトン定義マクロ
// マクロ使用後は public になるので注意
#define DEFINE_SINGLETON(TYPE_)\
private:\
    using self_type = TYPE_;\
    using unique_ptr = std::unique_ptr<self_type>;\
public:\
    using singleton = const unique_ptr &;\
    static singleton instance() {\
        return ref();\
    }\
    static void create() {\
        if (!ref()) ref() = make_unique();\
    }\
    static void destroy() {\
        ref().reset();\
    }\
private:\
    template <typename... Args>\
    static unique_ptr make_unique(Args&&... args) {\
        struct temp : self_type { temp() : self_type() {} };\
        return std::move(unique_ptr(new temp(std::forward<Args>(args)...)));\
    }\
    static unique_ptr &ref() {\
        static unique_ptr p = make_unique();\
        return p;\
    }\
public:

テスト

適当なクラスを定義し、マクロを使用してテストしました。

ソース

singleton-test.cpp
#include "singleton.h"
#include <stdio.h>

// 適当シングルトンクラス1
class device {
public:
    DEFINE_SINGLETON(device);

    void foo() { printf("device foo!\n"); }
    ~device() { printf("~device()\n"); }

private:
    // コンストラクタは private に
    device() { printf("device()\n"); }

    // 一応 Non copyable
    device(const device &) = delete;
    device &operator =(const device &) = delete;
};

// 適当シングルトンクラス2(名前と出力テキストが違うのみ)
class beacon {
public:
    DEFINE_SINGLETON(beacon);
    void foo() { printf("beacon bar!\n"); }
    ~beacon() { printf("~beacon()\n"); }
private:
    beacon() { printf("beacon()\n"); }
    beacon(const device &) = delete;
    beacon &operator =(const device &) = delete;
};

// 適当シングルトンクラス3(名前と出力テキストが違うのみ)
class gadget {
public:
    DEFINE_SINGLETON(gadget);
    void foo() { printf("gadget baz!\n"); }
    ~gadget() { printf("~gadget()\n"); }
private:
    gadget() { printf("gadget()\n"); }
    gadget(const device &) = delete;
    gadget &operator =(const device &) = delete;
};

int main() {
    printf("main begin\n");

    // 初回のインスタンス取得時に、自動でインスタンス化する
    printf("1: ");
    device::instance()->foo();

    // 明示的に受け取り、所有判定も行う
    printf("2: ");
    if (device::singleton p = device::instance()) {
        p->foo();
    }

    // auto を使うときは明示的に参照にする
    printf("3: ");
    if (auto &p = device::instance()) {
        p->foo();
    }

    // インスタンスを明示的に解放
    device::destroy();

    // 2回目以降のインスタンス取得時は有効とは限らない
    printf("4: ");
    if (device::singleton p = device::instance()) {
        p->foo();

    } else {
        printf("invalid instance.\n");
    }

    // インスタンスを明示的に作成
    printf("5: ");
    device::create();

    // できれば所有判定は行ったほうがよい
    printf("6: ");
    if (device::singleton p = device::instance()) {
        p->foo();
    }

    // 二重に読んでも二重に作成されない
    printf("7: ");
    beacon::create();
    beacon::create();

    printf("8: ");
    if (gadget::singleton p = gadget::instance()) {
        p->foo();
    }
    printf("9: ");
    if (beacon::singleton p = beacon::instance()) {
        p->foo();
    }

    printf("main end\n");

    // 解放順は初回のインスタンス化を行った逆順
    // 途中で destroy して create しても変わらない

    return 0;
}

出力

以下のように出力されます。

main begin
1: device()
device foo!
2: device foo!
3: device foo!
~device()
4: invalid instance.
5: device()
6: device foo!
7: beacon()
8: gadget()
gadget baz!
9: beacon bar!
main end
~gadget()
~beacon()
~device()

課題

実用的かどうか

とりあえず作ってみただけで実際には使用していません。
どなたかもし使用してみて、ご感想を頂けると幸いです。

テンプレートクラスなどにできないか

マクロで作ってみましたが、他にテンプレートクラスにして継承させるなりシングルトンホルダーにするなりの実装方法があると思います。

スレッドセーフかどうか

マルチスレッド周りは知識が不足しているため、スレッドセーフかどうか保証できません。
もしお分かりの方がいらっしゃればご指摘いただけると助かります。

2014-07-17 追記
コメントの方でご指摘頂きました。

yohhoy さんのコメントその2

異なるスレッドから同時にcreate()/destroy()/instance()を呼び出すと、データ競合による未定義動作になる

yohhoy さんのコメントその2

もちろん、利用者側でinstance()destroy()が同時に呼び出される事がないことを保証(排他制御)すれば良いのですが、本来やりたかったであろう「利用者側では排他制御を気にせずにinstance()でシングルトンインスタンスを取得」には反している気がします。この制限事項を許容するならば、明示的破棄/生成インターフェイス公開もアリだとは思います。

というわけで、現在上記のコードはスレッドセーフではありません。
マルチスレッド環境で使用する際はご注意ください。

15
13
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13