Singletonクラスの内部で持つポインタの話

先日の記事でコメントを頂いたのでちょっとSingletonクラスの内部で持つポインタについて自分なりに色々調べてみました。

まず下記のようなシンプルなSingletonクラスを考えます。

#include <iostream>
using namespace std;

class Foo {
private:
    static Foo* m_instance;
    Foo() {
        cout << "Foo" << endl;
    };
    ~Foo() {
        cout << "~Foo" << endl;
    };
public:
    static Foo* getInstance() {
        if ( !m_instance ) m_instance = new Foo;
        return m_instance;
    }
    static void destroy () {
        cout << "Foo::destroy" << endl;
        delete m_instance;
    }
};
Foo* Foo::m_instance = 0;

int main () {
    Foo* foo = Foo::getInstance();

    // 解放する
    Foo::destroy();

    return 0;
}
$ main
Foo
Foo::destroy
~Foo

Foo::destroyを呼ぶと、Fooが解放されます。

しかし、もしFoo::destroyが呼ばれなかった場合どうなるか?

int main () {
    Foo* foo = Foo::getInstance();

    // 解放処理を呼ばない
    // Foo::destroy();

    return 0;
}
$ main
Foo

このように、Fooのデストラクタが呼ばれません。

なのでもしFoo::destroyを呼び忘れたとしても自動で解放されてくれるように実装してみましょう。

using namespace std;

class Foo {
private:
    class Auto {
    public:
        Foo* m_instance;
        Auto() : m_instance(0) {}
        ~Auto() {
            destroy();
        }
        void destroy() {
            cout << "Foo::Auto::destroy" << endl;
            if ( m_instance ) {
                delete m_instance;
                m_instance = 0;
            }
        }
    };
    static Foo::Auto m_auto;
    Foo() {
        cout << "Foo" << endl;
    };
    ~Foo() {
        cout << "~Foo" << endl;
    };
public:
    static Foo* getInstance() {
        if ( !m_auto.m_instance ) m_auto.m_instance = new Foo;
        return m_auto.m_instance;
    }
    static void destroy () {
        cout << "Foo::destroy" << endl;
        m_auto.destroy();
    }
};
Foo::Auto Foo::m_auto;

int main () {
    Foo* foo = Foo::getInstance();
    
    // 解放処理を呼ばない
    // Foo::destroy();

    return 0;
}
$ main
Foo
Foo::Auto::destroy
~Foo

Fooクラスでポインタを保持するのではなくて、Foo::Autoという実体を用意し、そこのデストラクタでFooのポインタを解放するという方法です。

ちゃんとFooのデストラクタが呼ばれていますね。

またこの方法を逆手に取れば、設計ポリシーとして必ずFoo::destroyを呼んでねということをある程度強制させることも可能です。

#include <iostream>
using namespace std;

class Foo {
private:
    class Auto {
    public:
        Foo* m_instance;
        Auto() : m_instance(0){
        }
        ~Auto() {
            if ( m_instance ) {
                // Autoのデストラクタ呼び出しまでにm_instanceがまだ残っていたら
                // Foo::destroyが呼ばれていないので死んだりしよう。
                cout << "destroyが呼ばれないまま終了しました!" << endl;
            }
        }
        void destroy() {
            cout << "Foo::Auto::destroy" << endl;
            if ( m_instance ) {
                delete m_instance;
                m_instance = 0;
            }
        }
    };
    static Foo::Auto m_auto;
    Foo() {
        cout << "Foo" << endl;
    };
    ~Foo() {
        cout << "~Foo" << endl;
    };
public:
    static Foo* getInstance() {
        if ( !m_auto.m_instance ) m_auto.m_instance = new Foo;
        return m_auto.m_instance;
    }
    static void destroy () {
        cout << "Foo::destroy" << endl;
        m_auto.destroy();
    }
};
Foo::Auto Foo::m_auto;

int main () {
    Foo* foo = Foo::getInstance();
    return 0;
}
$ main
Foo
destroyが呼ばれないまま終了しました!

といった感じ。

個人的には自動で解放されてくれる方が楽なのでdestroy自体必要ないかなという気はしますが。

ただコメントくれた方が言ってた「解放順によっては意図しない動作を引き起こしたりするので、なかなか難しい話ではありますが。」というのが具体的にどういう場合なのかイメージがつきませんでした。

Singletonクラスの中で別のSingletonクラスに依存しててどうのこうのとかかな?と思ったりもしたけど例が思い浮かばなかったのでとりあえずスルーということで。