読者です 読者をやめる 読者になる 読者になる

Singletonを実現するgetInstanceが推奨できない理由

c/c++

id:nicht-seinさんからコメントがあったので書いてみます。
まず、コメントにも書いてくださったようによく見かけるこれ
(僅かに手を加えました)

class CHoge {
private:
    CHoge() : value_(100) {};
    ~CHoge(){};
    int value_;
public:
    void setHoge(int value) { value_ = value; }
    int getHoge() const { return value_; }
    static CHoge& getInstance() {
        static CHoge instance_;
        return instance_;
    }
};

他からnewされないようにコンストラクタがprivateになってます。
勿論、スタックに置くためにCHoge hoge;もできません。
でも、実はこれはインスタンス化できます。

    printf("%d\n", CHoge::getInstance().getHoge() );
    CHoge* hoge = new CHoge(CHoge::getInstance());
    hoge->setHoge(999);
    printf("%d\n", hoge->getHoge() );
    printf("%d\n", CHoge::getInstance().getHoge() );

こんな感じ。
実行すると、インスタンスが二つあることがわかる。
なぜnewできるのかというと、暗黙のコピーコンストラクタがあるから。
(nicht-seinさんが挙げた例は単に省略していた可能性があります)
結局、これらを完全に防御する手段をするのは割と面倒なことです。
また、こうしたクラスを作っても、
似たような機能を持つSingletonでないクラスをつくられたら終わりです。

で、結論はそういうことではなくて、

結局のところ、
安易にgetInstanceのクラスを増やすと
グローバル変数」を増やしているのと変わらないということ
それは要するに「密結合」であり、
「依存度の高さを不用意に高めている」ことに他ならない。

だいたい、
「本当にインスタンスが一つであることを完全に保証しなければならない」
ことってそもそもそんなに多発するケースなんだろうか?
そして、そのためにgetInstanceが溢れる世の中になっていいの?

if (Hoge::getInstance().getHoge() > 0) {
    if (Foo::getInstance().isFoo() && Bar::getInstance().isBar()) {
        ...
    }
}

極端な例だけれども、
こんなプログラムは見たくない。

if (Hoge().getHoge() > 0) {
    if (Foo().isFoo() && Bar().isBar()) {
        ...
    }
}

でも本質は一緒だ。
元々、Singletonは
「そのクラス自身を見たとき」
「そのインスタンスが一つしかない事を『表現』する」
ためにあるのであって、

他のクラスから見たとき、それが一つであるかどうかはどうでもいいし、
他のクラスからお手軽に(グローバル的に)アクセスすることを赦すために存在するわけじゃない。
そのインスタンスが単一のものであるかどうかなんてことは、
「そのクラス内で閉じているべき」なのだ。

ちょっと趣旨は違うけど

Singletonは、所詮、羊の皮を被ったグローバル変数であり、
相互依存や循環依存によって壊されることに注意しよう。

(初期化の依存関係について、C++ Coding Standardsより抜粋)