datchの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】シングルトンパターンについて

どうも、今週で卒論発表が終わり研究室で追いコンが開催されました。
人が離れて居なくなるのは寂しいことですね。

自分の隣の席の後輩が非常にイケメンなのですが、そのイケメンとも顔を合わせられなくなるのか…

などという、内輪ネタはさておき、今回は題名にある通りシングルトンパターンについて解説していきます。
現在、研究室で自分の研究のC++コードを書き直しているのですが、JavaDocのようなドキュメントや様々なデザインパターン、Boost、OpenCVOpenMPICC、Gitなどのあらゆる技術を駆使したまさに今の自分の技術・知識の結晶のようなコードにしたいと思いながら書いています。
その中で機能毎にクラス化しているのですが、もちろん研究用のコードなので機能というのはパラメータを除いては、単一のインスタンスしか生成されないので、ついでだからシングルトンの勉強ついでに利用してます(といっても、元のコードがたかだか2000行程度のコードなのでわざわざこんなの使う意味もないんだけど…)

本当はCentOSの記事を書こうと思ったんだけど、本に書いてあるのは最初はリファレンスレベルの内容なので記事を書く必要もない感じでちょっと困ってます。
家じゃなくて学校のサーバ弄って遊べばいいか?

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。

(翻訳が酷いってことでレビュー低くて悲しい)

シングルトン(Singleton)って何?



一つのプロセス上、つまりは実行中のプログラム上でインスタンスが一つしか生成されないことを約束されているデザインパターンです。

別にstaticなクラスにすれば同じ要件満たせない?



はい、仰るとおりです。実を言うと、私も現在リファクタリングしている研究用のコードで最初の内はstaticなクラスを作成して利用していました。
その後、なんとなしにSingletonパターンを使い始めたのですが、よくよく考えてみるとどっちも出来る事は変わらないのに違いは?って思い、どうせだから記事を書いてる感じです。
正直申し上げると、私はそこまでSingletonとstaticなクラスのありがたみの違いをあまり理解してないですが、知っている範囲で書きたいと思います。

何が嬉しいの?


といった恩恵を受けることが出来る。

シングルトンパターンの実装方法



それじゃ、実際にどうやってプログラム中にインスタンスがひとつしか生成されないようにするか、コードを見てみましょう。

class Singleton
{
private:
    Singleton(){};
    static Singleton* uniqueInstance = NULL;
public:
    static Singleton* getInstance()
    {
        if(uniqueInstance != NULL)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

これだとポインタ使っててJavaっぽい雰囲気あるのでこんな書き方も出来るよ。

class Singleton
{
private:
    Singleton(){};
public:
    static Singleton& getInstance()
    {
        static Singleton uniqueInstance;
        return uniqueInstance;
    }
};

これだとシンプルだと後述するマルチスレッドプログラミングで面倒なケースも考える必要がないし、コードも短いのでデフォルトコンストラクタのみならこちらのほうで問題ないと思います。

それじゃ、このコードが一体に何をしているのか?
どうしてインスタンスの数が一つだけと保証されるのか?
その仕組みについて解説していく。

Singletonの仕組み


privateなコンストラクタ

では、もう一度上のソースコードを見てみよう。

class Singleton
{
private:
    Singleton(){};
    static Singleton* uniqueInstance = NULL;
public:
    static Singleton* getInstance()
    {
        if(uniqueInstance != NULL)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
};

変だと思った人もいるかもしれないが、コンストラクタがprivateで宣言されている。

実はこのコンストラクタをprivateにすると、Singletonクラスの外ではコンストラクタが呼び出されなくなり、newで新しいインスタンスが作れなくなります。

getInstanceのifとstatic宣言

そして、次にgetInstance()メソッドに注目。

if(uniqueInstance != NULL)
{
    uniqueInstance = new Singleton();
}
return uniqueInstance;

重要なのはこのif文とprivate staticで宣言されているuniqueInstance変数です。
staticで宣言されることでuniqueInstanceはSingletonのクラス変数になり、唯一のフィールドとして定義されます。

そして、uniqueInstanceはgetInstance()が初めて呼び出された時のみ、if文の中に入ってインスタンスを生成し、次回以降のgetInstance()では既に生成済みのインスタンスが返されます。

この2つの工程を取ることでシングルトンパターンを実現することで出来ました。
逆に言えば、この2つを行わないとシングルトンパターンは途端に成立しなくなるので注意しましょう。

ちなみに私はコピペでコンストラクタをpublicで宣言してやらかしました←

シングルトンパターンのマルチスレッドプログラミングでの問題点



では、先ほどのコードをマルチスレッドで呼び出された時、本当に唯一のインスタンスが返されるのでしょうか?
もし、スレッドが同時にif文の中に突入したら?
帰ってくるインスタンスって本当に同じものなのか。
答えはNOです。

発生する確率は稀かもしれないですが、結果が必ず同じでないというのはかなり問題ですね。
となると、スレッドの同期などを行う必要があります。

スレッドセーフなシングルトンパターン



C/C++ではスレッドの扱いが面倒なのでこちらはJavaコードで記述します。
以下のように書くことで唯一のインスタンスが保証されます。

class Singleton
{
    static Singleton uniqueInstance = null;
    private Singleton(){};
    public static Singleton synchronized getInstance()
    {
        if(uniqueInstance != null)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

そう、単にsynchronized文を追加しただけです。
思った以上に簡単にシングルトンパターンが実現出来ましたね。
これでみなさんもシングルトンパターンについて最低限は語れるようになりました。

終わりに



参考書を読んでたらもっとOOに乗っ取ったコードを書けるやり方がどんどん思いついたので、面白そうだと思って色々と考えてたら途中でどんな内容を書くか忘れてしまい、グダグダになってしまいました。
このような文章を最後まで読んで頂き、ありがとうございました。