Singleton デザインパターンとかポインタとか

何回かコメントを書かせてもらった、
Singletonクラスにauto_ptrを使うのは案外難しい - (void*)Pないと
こちらの話題。


「auto_ptrからFooを破棄されるかもしれないじゃない」とのことですが、
これは一応 std::unique_ptr を使うことで回避できます(要 C++0x 対応コンパイラ):

#include <iostream>
#include <memory>

// 好みの書き方に書き直してるけど基本的には変わらない。
class Foo
{
  typedef Foo self_type;
  
 public:
  static self_type* getInstance()
  {
    if( !instance_ )
    {
      instance_.reset( new self_type );
    }
    return instance_.get();
  }
  
  // ただ、それだけだとなんとなく悲しいので、メンバ関数も作ってみる
  void bar()
  {
    std::cout << "bar\n";
  }
    
 private:
  // カスタム削除子。インスタンスの削除はここからのみ行なう。
  struct deleter
  {
    void operator()( self_type const* const p ) const
    {
      delete p;
    }
  };
  // std::unique_ptr は削除子を指定出来る
  typedef std::unique_ptr<self_type, deleter> instance_type;
  static instance_type instance_;
  
  Foo()
  {
    std::cout << "Foo\n";
  }
  ~Foo()
  {
    std::cout << "~Foo\n";
  }
  
};
Foo::instance_type Foo::instance_;
 
int main()
{
  Foo* foo = Foo::getInstance();
  foo->bar(); // メンバ関数呼び出し
  // delete foo;                              // コンパイルエラー!
  // std::auto_ptr<Foo>(foo);                 // コンパイルエラー!
  // std::unique_ptr<Foo>(foo);               // コンパイルエラー!
  // std::unique_ptr<Foo, Foo::deleter>(foo); // コンパイルエラー!
  return 0;
}

肝は、unique_ptr にカスタム削除子を持たせられる、という点ですね。
何のことはない、
http://www.boost.org/doc/libs/1_42_0/libs/smart_ptr/sp_techniques.html#preventing_delete
で紹介されていたテクの unique_ptr 版です。地味に便利。


しかし、個人的に思うのは、それは真の解決ではない、ということです。
シングルトンまとめ - Togetter にまとめてみましたが、
先のコードで真に問題なのは、 getInstance が生ポインタを返している点だ、ということです。


もしここで、 getInstance がポインタではなく参照を返す仕様だとしたら、
main 関数内部の処理は、こうなります。

int main()
{
  Foo& foo = Foo::getInstance();
  foo.bar();  // メンバ呼び出しはこっちが自然。
  // delete &foo; // これは明らかにおかしい!
}


どうでしょう。ポインタと違って、うっかり delete するのは躊躇われる筈です。


この点を考慮して書き直したコードが、以下のものとなります。
http://ideone.com/uhz1m
C++0x ではなく現行の C++03 の規格に準拠したコードは、以下の通り。
http://ideone.com/RURyQ

いずれもインスタンスの取得に参照を使うことで、
「クラス Foo の唯一のインスタンスへの参照を取得している」という意図を明確にしています。


そもそも、生ポインタというものは、 C++ において非常に厄介なシロモノです。
生ポインタを使う事自体が「間違ってる」デザインとまでは言いませんが、
かつてポインタというものが様々な文脈で使われてきた以上、どうしても意図の明確さに劣ってしまいます。

C++ には、より明確に意図を表明できる「参照」や「スマートポインタ」というものが導入されているのです。
生のポインタを使う特別な理由が無い限りは、そちらを使うほうが自然にコードが書けるはず。

コードを工夫してうっかりミスを予防する、という方法も大事ですが、
しかし「うっかりミスが起こり得る」設計、というのは、設計自体が良くない場合も多いのです。
小手先の技術にとらわれず、どうやれば本質的にミスを防げるか、ということを常に考えていきたいですね。




ちなみにシングルトン自身の実装ですが、個人的に
一番良いデザインは @yamasapost してくれた

  static self_type& getInstance()
  {
    static self_type instance;
    return instance;
  }

というものだと思います。