Singletonクラスにauto_ptrを使うのは案外難しい

またまたSingletonクラスの話です。昨日の続きです。

自動でデストラクタを呼ばせる目的なら、自前でクラスを作るよりもauto_ptrを使った方が意図が伝わりやすいというコメントを頂き、確かにそうだなと思ったのでauto_ptrを使ってもう一度Singletonクラスを実装してみました。

#include <iostream>
#include <memory>
using namespace std;

class Foo {
private:
    static auto_ptr<Foo> m_auto;
    Foo() {
        cout << "Foo" << endl;
    };
    ~Foo() {
        cout << "~Foo" << endl;
    };
public:
    static Foo* getInstance() {
        if ( !m_auto.get() ) m_auto = auto_ptr<Foo>(new Foo);
        return m_auto.get();
    }
};
auto_ptr<Foo> Foo::m_auto;

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

さてこのコード、前回の記事ではFoo::Autoという内部クラスで自動デストラクタをしていましたが、それをauto_ptrに変えただけのものです。

コンパイルしてみるとエラーになりました。

$ cl /W4 /EHsc main2.cpp
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main2.cpp
main2.cpp(29) : warning C4189: 'foo' : ローカル変数が初期化されましたが、参照されていません
C:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\memory(721) : error C2248: 'Foo::~Foo' : private メンバ (クラス 'Foo' で宣言されている) にアクセスできません。
        main2.cpp(25) : コンパイラが 'Foo::~Foo' をここに生成しました。
        main2.cpp(6) : 'Foo' の宣言を確認してください。
        C:\Program Files\Microsoft Visual Studio 9.0\VC\INCLUDE\memory(720): クラス テンプレート のメンバ関数 'std::auto_ptr<_Ty>::~auto_ptr(void)' のコンパイル中
        with
        [
            _Ty=Foo
        ]
        main2.cpp(9) : コンパイルされたクラスの テンプレート のインスタンス化 'std::auto_ptr<_Ty>' の参照を確認してください
        with
        [
            _Ty=Foo
        ]

エラーの内容としてはFooクラスのデストラクタがprivateで宣言されているため、auto_ptrのデストラクタでFooのデストラクタを呼べません!というエラーです。

どうすればいいか。もしFooのデストラクタをpublicにしてしまったら、普通にどこでもdeleteされる恐れがあるのでそれはできません。

となればauto_ptrからのみFooのprivateにアクセスできるようにすればいいわけですね。そんな時に使うのがfriendです。

#include <iostream>
#include <memory>
using namespace std;

class Foo {
private:

    // auto_ptr<Foo>からはprivate領域にアクセスできるようにfriendクラス登録する
    friend class auto_ptr<Foo>;

    static auto_ptr<Foo> m_auto;
    Foo() {
        cout << "Foo" << endl;
    };
    ~Foo() {
        cout << "~Foo" << endl;
    };
public:
    static Foo* getInstance() {
        if ( !m_auto.get() ) m_auto = auto_ptr<Foo>(new Foo);
        return m_auto.get();
    }
};
auto_ptr<Foo> Foo::m_auto;

int main () {
    Foo* foo = Foo::getInstance();
    return 0;
}
$ main
Foo
~Foo

これで動くようになりました。これで完璧・・・・・かと思ったんですが、もう一つ問題がありました。

friend登録したのでauto_ptrからはいつでもFooのデストラクタにアクセスできるということは、以下のようなコードを書かれた場合、まずいことになります。

int main () {
    Foo* foo = Foo::getInstance();
    
    {
        // このスコープ内でauto_ptrを宣言する
        auto_ptr<Foo> tmp(foo);
    }
    
    cout << "end" << endl;
    return 0;
}
$ main
Foo
~Foo
end
~Foo

特定のスコープ内でauto_ptrにFooのポインタを渡してしまうとfriend登録してるのでデストラクタが呼ばれてしまうのです。

通常こんな書き方はしないし、してはいけないので問題ないといえばないのですが、悪意が有ろうと無かろうとFooクラスの管理下以外の場所でデストラクタが呼ばれてしまう可能性が残っているというのは問題かなぁという気もします。

この後、この問題を回避できないか色々考えたのですが良い方法を思いつきませんでした。もうちょっと考えよう。