单例模式(创建型)

前言


有很多时候,为了节约系统资源需要确保系统中某个类只有一个实例存在,当这个实例被创建成功后,无法再创建一个同类型的其他实例,如系统的日志输出,GUI应用必须是单鼠标,操作系统只能有一个窗口管理器,一台PC只能连一个键盘等等,为了确保对象的唯一性可以使用单例模式来实现。

单例模式


单例模式有许多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这样的代码显的很不优雅。 使用全局对象能够保证方便地访问实例,但是不能保证只声明一个对象——也就是说除了一个全局实例外,仍然能创建相同类的本地实例,单例模式有三个要点:

  • 某个类只能有一个实例;
  • 它必须自行创建这个实例;
  • 它必须自行向整个系统提供这个实例

意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点

参与者

  • Singleton
    定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作(C++中是一个静态成员函数);
    负责创建它自己的唯一实例

模式结构

singleton

代码实现

一般场景下单例模式

定义一个Singleton类,包含一个静态的私有实例对象,并提供访问该唯一实例对象的全局访问静态方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Singleton
{
private:
static Singleton* m_pSingleton; // 静态对象指针
private: // 构造,拷贝构造,赋值函数均为私有
Singleton() {};
Singleton(const Singleton&) {};
Singleton& operator = (const Singleton&) {};
public:
static Singleton* GetInstance() // 静态全局访问点
{
if (NULL == m_pSingleton)
{
m_pSingleton = new Singleton();
}
return m_pSingleton;
}
}
Singleton* Singleton::m_pSingleton = NULL;

用户访问唯一实例的方法只有GetInstance()函数,如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的;
GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的,这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针:
CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();

GetInstance()稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例:在GetInstance()函数添加入参,根据入参决定实例化那种类型的实例

多线程场景下单例模式(改进1)

上面是一个简单的单例模式,但是还存在缺陷,对于多线程的情况需要考虑线程安全问题,可以考虑上锁和类的静态变量初始化两种方式解决:

  1. 上锁(懒汉式单例)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Singleton
    {
    private:
    static Singleton* m_pSingleton; // 静态对象指针
    private: // 构造,拷贝构造,赋值函数均为private或protected(有子类)
    Singleton() {};
    Singleton(const Singleton&) {};
    Singleton& operator = (const Singleton&) {};
    public:
    static Singleton* GetInstance() // 静态全局访问点
    {
    if (NULL == m_pSingleton)
    {
    m_CS.Lock(); // 上锁
    if (NULL == m_pSingleton)
    {
    m_pSingleton = new Singleton();
    }
    m_CS.UnLock(); //解锁
    }
    return m_pSingleton;
    }
    public: // 上锁
    static CCriticalSection m_CS;
    }
    Singleton* Singleton::m_pSingleton = NULL;

此处进行了两次m_Instance == NULL 的判断,使用了的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,性能降低就可以避免多次加锁与解锁操作,同时也保证了线程安全

  1. 静态变量初始化(饿汉式单例)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Singleton
    {
    private:
    static Singleton* m_pSingleton; // 静态对象指针
    private: // 构造,拷贝构造,赋值函数均为private或protected(有子类)
    Singleton() {};
    Singleton(const Singleton&) {};
    Singleton& operator = (const Singleton&) {};
    public:
    static Singleton* GetInstance() // 静态全局访问点
    {
    return m_pSingleton; // 此处直接返回单一实例对象指针
    }
    }
    // 此处进行静态变量初始化
    Singleton* Singleton::m_pSingleton = new Singleton;

因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费

唯一对象指针内存释放场景下单例模式(改进2)

在有些场景的类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放,一般情况下有下面几种解决方案:

  1. 在程序结束时候调用GetInstance()并对返回的指针用delete操作

  2. 可以在类中添加静态成员比那里,因为程序结束的时候,系统会自动析构所有的全局变量(调用其析构函数),事实上,系统也会析构所有的类的静态成员变量,就像这些静态成员也是全局变量一样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    class Singleton
    {
    private:
    static Singleton* m_pSingleton; // 静态对象指针
    private: // 构造,拷贝构造,赋值函数均为private或protected(有子类)
    Singleton() {};
    Singleton(const Singleton&) {};
    Singleton& operator = (const Singleton&) {};
    public:
    static Singleton* GetInstance() // 静态全局访问点
    {
    return m_pSingleton; // 此处直接返回单一实例对象指针
    }
    private:
    //嵌套类CGarbo它的唯一工作就是在其析构函数中删除CSingleton的实例 ,
    class CGarbo
    {
    public:
    ~CGarbo()
    {
    //为了访问Singleton类的private类型变量Singleton::m_pSingleton,可以将其声明为友元或改为Singleton::GetInstance();
    if (Singleton::GetInstance())
    {
    delete Singleton::GetInstance();
    }
    }
    }
    //// 声明静态变量,如果不定义成static,Garbo就是*m_pSingleton实例的一部分,只有当这个析构的时候,Garbo才能析构。而本意是要用Garbo去析构前面那个实例
    static CGarbo m_Garbo;
    }
    // 此处进行静态变量初始化
    Singleton* Singleton::m_pSingleton = new Singleton;
    //定义用于析构单例而添加的类静态变量成员,但未初始化
    Singleton::CGarbo Singleton::m_Garbo;
  3. 使用局部静态变量(也可以解决多线程安全问题)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Singleton
    {
    private: // 构造,拷贝构造,赋值函数均为private或protected(有子类)
    Singleton() {};
    Singleton(const Singleton&) {};
    Singleton& operator = (const Singleton&) {};
    public:
    static Singleton* GetInstance() // 静态全局访问点
    {
    static Singleton singleton;
    return &singleton; // 此处直接返回局部静态变量的引用
    }
    }

但使用此种方法也会出现问题,当不满足bitwise拷贝时候,编译器会为类生成一个默认的构造函数,来支持类的拷贝,这可以通过显式的声明类拷贝的构造函数,和重载=操作符来解决,只声明,实现为空。

  1. 测试单例模式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void SingletonTest()
    {
    Singleton *pS1 = Singleton::GetInstance();
    Singleton *pS2 = Singleton::GetInstance();
    if (pS1 == pS2)
    {
    cout << "the same object" << endl;
    }
    else
    {
    cout << "not the same object" << endl;
    }
    }

运行结果:

the same object

使用场景

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展实例时

优缺点

  • 优点:
    • 对唯一实例的受控访问
    • 缩小名空间,是对全局变量的一种改进
    • 允许对操作和表示的精化;单例模式类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。你可以用你所需要的类的实例在运行时刻配置应用
    • 允许可变数目的实例
  • 缺点:
    使用泛滥???

单例模式具体实例


使用单例模式创建一个简单的类似于Windows系统的任务管理器类,包含简单的显示进程和服务

代码实现

  1. TaskManager类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class TaskManager
    {
    private: // 私有构造函数
    TaskManager(){}
    private: // 私有的唯一实例
    static TaskManager* m_ptaskmgr;
    public: // 公有的唯一实例访问点
    static TaskManager* getInstance()
    {
    static TaskManager taskmgr; // 局部静态变量
    return &taskmgr;
    }
    public:
    void displayProcesses()
    {
    cout << "Task Mgr Process" << endl;
    }
    void displayServices()
    {
    cout << "Task Mgr Services" << endl;
    }
    };
  2. 测试Task Manager:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    void TaskMgrTest()
    {
    TaskManager* ptm1 = TaskManager::getInstance();
    TaskManager* ptm2 = TaskManager::getInstance();

    if (ptm1 == ptm2)
    {
    cout << "Single Task Mgr" << endl;
    ptm1->displayProcesses();
    ptm1->displayServices();
    }
    else
    {
    cout << "Not Single Task Mgr" << endl;
    }
    }

运行结果:

Single Task     
MgrTask Mgr 
ProcessTask Mgr Services