单例模式(Singleton Pattern,也称为单件模式),使用最广泛的设计模式之一。
单例模式就是一个类只能被实例化一次 ,更准确的说是只能有一个实例化的对象的类,该实例被所有程序模块共享。
类似于全局变量。
有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,MODEM的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台PC连一个键盘等。
使用场景:
通过以上分析,要实现单例模式,需要做到以下几点:
那么,单例类的构造函数就要是私有的,可以考虑如下步骤:
根据这个思路的实现如下:
// 不完善的类
class Singleton
{
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton* instance;
public:
static Singleton* getInstance()
{
if (instance == NULL)
instance = new Singleton();
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = NULL;
仔细观察,会发现这个例程只创建了对象,而没有释放,存在内存泄漏的问题。
有两种解决办法:
嵌套类对象的实现如下:
// 不完善的类
class Singleton
{
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton* instance;
private:
class Deletor
{
public:
~Deletor()
{
if (Singleton::instance != NULL)
delete Singleton::instance;
}
};
static Deletor deletor;
public:
static Singleton* getInstance()
{
if (instance == NULL)
instance = new Singleton();
return instance;
}
};
// 初始化静态成员
Singleton* Singleton::instance = NULL;
在程序运行结束时,系统会调用静态成员deletor的析构函数,该析构函数会删除单例的唯一实例,方法有如下特征:
这个程序在单线程环境下是能够运行的,但是对于多线程,则可能会创建多个实例,如多个线程同时进入if (instance == NULL)
语句。
最简单的办法是使用锁保证线程安全,代码如下:
static Singleton* getInstance()
{
if (instance == NULL)
{
Lock lock;
if (instance == NULL)
instance = new Singleton();
}
return instance;
}
如上的双检测锁模式(DCL: Double-Checked Locking Pattern),在保证线程安全的情况下又尽量减少对运行效率的影响。
但是,该实现仍然存在问题,设想一个线程运行到了第7行,另一个线程运行到第3行,发现instance已经就绪并返回实例时,然而该实例还未完成构建时,会发生错误。
换句话说,就是代码中第2行:if(instance == NULL)和第六行instance = new Singleton();没有正确的同步。
在C++11没有出来的时候,只能靠插入两个memory barrier(内存屏障)来解决这个错误,但是C++11引进了memory model,提供了Atomic实现内存的同步访问,即不同线程总是获取对象修改前或修改后的值,无法在对象修改期间获得该对象。
另外,C++11规定了local static在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性。
这样,只有当第一次访问getInstance()方法时才创建实例。这种方法也被称为Meyers’ Singleton。C++0x之后该实现是线程安全的,C++0x之前仍需加锁。
// 完善的类
class Singleton
{
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
public:
static Singleton& getInstance()
{
static Singleton instance;
return instance;
}
};
以上实现在单例实例第一次被使用时才进行初始化,即延迟初始化,称为懒汉模式(Lazy Singleton)。
饿汉版(Eager Singleton):指单例实例在程序运行时被立即执行初始化。
// 饿汉版本
class Singleton
{
private:
Singleton() {};
~Singleton() {};
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton instance;
public:
static Singleton& getInstance()
{
return instance;
}
};
// 默认直接初始化
Singleton Singleton::instance;
由于在main函数之前初始化,所以没有线程安全的问题。
但是潜在问题在于no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的。也即,static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。
正因为单例模式使用广泛,所以在面试中也是最常被问到的问题。
在实际学习中,应结合实际应用使用,学以致用,能够更加深刻地理解单例模式。
C++ 单例模式