SingletonじゃないクラスをSingleton化する方法を考える

もともとSingletonではない元クラスをSingletonとして扱う方法がないかを考えてみる。

条件として,
元クラスは引数無しのコンストラクタを呼べなくてはならない。また、当然のことながら元クラス単体を定義したりnewしたりできるということはここでは問題としない。

ということで早速実装してみる。

#include <iostream>
using namespace std;

class Foo {
public:
    Foo() {
        cout << "Foo" << endl;
    }
    ~Foo() {
        cout << "~Foo" << endl;
    }
    void output () {
        cout << "Foo::output" << endl;
    }
};

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

template<class Target>
SingletonWrapper<Target>* SingletonWrapper<Target>::m_instance = 0;

int main () {
    typedef SingletonWrapper<Foo> FooSingleton;
    FooSingleton* foo = FooSingleton::getInstance();
    
    foo->output();
    
    FooSingleton::destroy();
    
    return 0;
}
$ main
Foo
SingletonWrapper
Foo::output
~SingletonWrapper
~Foo

ターゲットとなるFooクラスをパラメータ化継承するSingletonWrapperというテンプレートクラスを用意する方法。

ばっちりうまくいっている。もしmain関数内で

    delete foo;

と書いたとしてもSingletonWrapperのデストラクタがprivateなのでエラーとなる。



・・・だがしかし!問題があった。

SingletonWrapperはFooを継承しているのでアップキャストしてdeleteされたら終わりだった。

int main () {
    typedef SingletonWrapper<Foo> FooSingleton;
    FooSingleton* foo = FooSingleton::getInstance();
    
    // アップキャスト
    Foo* obj = foo;

    // 動いてしまう・・・
    delete obj;
    
    return 0;
}

ということでパラメータ化継承ベースのSingleton化は無理があるようだ。

なので次に考えたのが包含。

さっそく実装してみる。

#include <iostream>
using namespace std;

class Foo {
public:
    Foo() {
        cout << "Foo" << endl;
    }
    ~Foo() {
        cout << "~Foo" << endl;
    }
    void output () {
        cout << "Foo::output" << endl;
    }
};

template<class Target>
class SingletonWrapper {
public:
    static SingletonWrapper<Target>& getInstance() {
        if ( !m_instance ) m_instance = new SingletonWrapper<Target>();
        return *m_instance;
    }
    
    static void destroy() {
        if ( m_instance ) {
            delete m_instance;
            m_instance = 0;
        }
    }
    
    Target* operator->() {
        return &m_target;
    }
private:
    Target m_target;
    static SingletonWrapper<Target>* m_instance;
    
    SingletonWrapper() {
        cout << "SingletonWrapper" << endl;
    }
    
    ~SingletonWrapper() {
        cout << "~SingletonWrapper" << endl;
    }
};

template<class Target>
SingletonWrapper<Target>* SingletonWrapper<Target>::m_instance = 0;

int main () {
    typedef SingletonWrapper<Foo> FooSingleton;
    FooSingleton& foo = FooSingleton::getInstance();
    
    foo->output();
    
    FooSingleton::destroy();
    
    return 0;
}
$ main
Foo
SingletonWrapper
Foo::output
~SingletonWrapper
~Foo

うまくいった。

Fooを継承してるわけではないのでアップキャストも無理なのでこれでOKだろう。

と、思ったものの、もしFooクラスにおいて自身のポインタを返すようなメンバ関数が定義されていた場合、それ経由でdeleteされてしまう可能性もある。

継承ベースだとアップキャスト問題があるし、包含だと自身のポインタ問題がある。どちらも一長一短か。

暗黙のアップキャストをエラーにするような宣言とかあればいいんだけどなぁ。

とりあえず俺の力ではここまでか。