Singleton单件和仿单件的各种做法探讨

来源:互联网 发布:中国人口老龄化数据 编辑:程序博客网 时间:2024/04/28 23:12
#include <iostream>
#include <string>
using namespace std;

////////////////////////////////////////////////////////////////////////////////
class Singleton {
public:
    static Singleton& Instance() {
        static Singleton instance;
        return instance;
    }

    void SetName( string name ) { name_ = name; }
    void ShowName() { cout << name_ << endl; };

private:
    string name_;

    Singleton() {}
    ~Singleton() {}
    Singleton( const Singleton& rhs );
    Singleton& operator=( const Singleton& rhs );
};

////////////////////////////////////////////////////////////////////////////////
class MonostateFromMartin {
public:
    void SetName( string name ) { name_ = name; }
    void ShowName() { cout << name_ << endl; };

private:
    static string name_;
};
string MonostateFromMartin::name_;

////////////////////////////////////////////////////////////////////////////////
class MonostateFromAlexandrescu {
public:
    static void SetName( string name ) { name_ = name; }
    static void ShowName() { cout << name_ << endl; };

private:
    static string name_;
};
string MonostateFromAlexandrescu::name_;

////////////////////////////////////////////////////////////////////////////////
class MonostateMixSingleton {
public:
    static void SetName( string name ) { Instance().name_ = name; }
    static void ShowName() { cout << Instance().name_ << endl; };

private:
    static MonostateMixSingleton& Instance() {
        static MonostateMixSingleton instance;
        return instance;
    }

    string name_;
};

////////////////////////////////////////////////////////////////////////////////
class MonostateComplex {
public:
    static void SetName( string name ) { impl.SetName( name ); }
    static void ShowName() { impl.ShowName(); };

private:
    class MonostateComplex_ {
    public:
        void SetName( string name ) { name_ = name; }
        void ShowName() { cout << name_ << endl; };

    private:
        string name_;
    };
    static MonostateComplex_ impl;
};
MonostateComplex::MonostateComplex_ MonostateComplex::impl;

////////////////////////////////////////////////////////////////////////////////
namespace {
    string name_;
}

namespace NamespaceSinglton {
    void SetName( string name ) { name_ = name; }
    void ShowName() { cout << name_ << endl; };
}

////////////////////////////////////////////////////////////////////////////////
template < class T >
class SingletonHolder {
public:
    static T& Instance() {
        static T instance;
        return instance;
    }
};

////////////////////////////////////////////////////////////////////////////////
void main() {
    {
        Singleton::Instance().SetName( "Singleton" );
        Singleton::Instance().ShowName();
    }


    {
        MonostateFromMartin m1;
        m1.SetName( "MonostateFromMartin" );

        MonostateFromMartin m2;
        m2.ShowName();
    }

    {
        MonostateFromAlexandrescu::SetName( "MonostateFromAlexandrescu" );
        MonostateFromAlexandrescu::ShowName();
    }

    {       
        MonostateMixSingleton::SetName( "MonostateMixSingleton" );
        MonostateMixSingleton::ShowName();
    }

    {
        MonostateComplex::SetName( "MonostateComplex" );
        MonostateComplex::ShowName();       
    }

    {
        NamespaceSinglton::SetName( "NamespaceSinglton" );
        NamespaceSinglton::ShowName();
    }


    {
        SingletonHolder< const char* >::Instance() = "abc";
    }
}

综合比较

除Singleton以外的做法习惯上我把他们叫做仿单件。

Singleton的做法是标准的单件做法,但是每次都要以Instance()调用,颇不方便
MonostateFromMartin的做法可以让多个对象拥有同一份数据。

后面的做法除了MonostateMixSingleton外都是类namespace(class+static)的做法,不如最后的 NamespaceSinglton来得简捷。但有一个问题,如果你要在main之前利用启动码(《C++设计新思维》p204)调用其静态成员函数(或者名 字空间的函数)就会产生初始化顺序未定义的问题,就是函数被调用时相应的静态变量(或者名字空间变量)可能尚未产生。比如:

bool startup() {
    NamespaceSinglton::SetName( "NamespaceSinglton" );
    return true;
}
bool startup__ = startup();

上述代码若在main()以外运行,可能会出错,也可能不会,其结果未定义。原因是SetName操作的name_变量在startup__产生时可能尚未产生。

解决方案是在名字空间中加入一个Mayers Singleton式的访问器来访问之

namespace {
    string& GetName() {
        static string name_;
        return name_;
    }
}

但是就namespace而言,你不得不对每个变量产生一个访问器,因为这些变量位于namespace而非class内,你无法产生一个类对象来统一使用这些变量。
如果是采用class+static的话,那么MonostateMixSingleton正是上述问题的解决方案。

结论

个人认为如果代码是写给自己用,那么Singleton是不错的选择,因为其实作简捷。另外在一些用到单件的设计模式中(比如State,Strategy等),标准用法也是Singleton(GOF就是这样用的)。
如果是写程序库,那么应该用MonostateMixSingleton,虽然其实现较为复杂,但具有易用的接口(不需要像Singleton那样每次都要调用Instance()),同时也没有全局变量初始化未定义的问题。

SingletonHolder其实就是Loki的做法,注意它并没有把目标类型变成单件,只是产生了目标类型的一个static变量而已。如果你不想自己实现Singleton并且有现成的Loki库使用,那么用这个方法也未尝不可。另外此法也没有全局变量初始化相依的问题。

补充

对于class+static的方式,你或许会将static放入实现文件中以匿名的namespace取代之,这样在头文件中的信息会隐藏的更深些。看其来是个不错的想法,但是 class+匿名namespace 的做法显然还未流行起来,鲜见于任何文献。为了避免你或者其他人产生混淆(怎么会有这么奇怪的东西?),建议你还是保持class+static的方式。

参考文献

《Effective C++第三版》chapter4
《C++设计新思维》chapter6
《敏捷软件开发》chapter16