单例模式很简单?但你真能写对吗?
保证一个类仅有一个实例,并提供一个该实例的全局访问点。——《设计模式》
-
Windows的Task Manager(任务管理器)就是很典型的单例模式,你不能同时打开两个任务管理器。Windows的回收站也是同理。 -
应用程序的日志应用,一般都可以用单例模式实现,只能有一个实例去操作文件。 -
读取配置文件,读取的配置项是公有的,一个地方读取了所有地方都能用,没有必要所有的地方都能读取一遍配置。 -
数据库连接池,多线程的线程池。
实现
实现一[线程不安全版本]
class Singleton{
public:
static Singleton* getInstance(){
// 先检查对象是否存在
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton( const Singleton& other);
static Singleton* m_instance; //静态成员变量
};
Singleton* Singleton::m_instance= nullptr; //静态成员需要先初始化
实现二[线程安全,锁的代价过高]
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
Lock lock; //伪代码 加锁
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
实现三[双检查锁]
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
//先判断是不是初始化了,如果初始化过,就再也不会使用锁了
if(m_instance== nullptr){
Lock lock; //伪代码
if (m_instance == nullptr) {
m_instance = new Singleton();
}
}
return m_instance;
}
-
分配了一个Singleton类型对象所需要的内存。 -
在分配的内存处构造Singleton类型的对象。 -
把分配的内存的地址赋给指针m_instance。
编译器可能会对代码进行调整优化,让那些看起来顺序不影响结果的代码进行顺序的调整,但是实际上在多线程中可能会有问题。
实现四[C++ 11版本的跨平台实现]
//C++ 11版本之后的跨平台实现
// atomic c++11中提供的原子操作
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;
/*
* std::atomic_thread_fence(std::memory_order_acquire);
* std::atomic_thread_fence(std::memory_order_release);
* 这两句话可以保证他们之间的语句不会发生乱序执行。
*/
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load( std::memory_order_relaxed);
std::atomic_thread_fence( std::memory_order_acquire); //获取内存fence
if (tmp == nullptr) {
std::lock_guard< std::mutex> lock(m_mutex);
tmp = m_instance.load( std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
std::atomic_thread_fence( std::memory_order_release); //释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
实现五[pthread_once函数]
int pthread_once(pthread_once_t once_control, void (init_routine) (void));
变量保证init_routine()函数在本进程执行序列中仅执行一次。
示例如下:
class Singleton{
public:
static Singleton* getInstance(){
// init函数只会执行一次
pthread_once(&ponce_, &Singleton::init);
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton( const Singleton& other);
//要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数)
static void init() {
m_instance = new Singleton();
}
static pthread_once_t ponce_;
static Singleton* m_instance; //静态成员变量
};
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
Singleton* Singleton::m_instance= nullptr; //静态成员需要先初始化
实现六[c++ 11版本最简洁的跨平台方案]
The initialization of such a variable is defined to occur the first time control passes through its declaration; for multiple threads calling the function, this means there’s the potential for a race condition to define first.
class Singleton{
public:
// 注意返回的是引用。
static Singleton& getInstance(){
static Singleton m_instance; //局部静态变量
return m_instance;
}
private:
Singleton(); //私有构造函数,不允许使用者自己生成对象
Singleton( const Singleton& other);
};
-
gcc 4.0之后的编译器支持这种写法。 -
C++11及以后的版本(如C++14)的多线程下,正确。 -
C++11之前不能这么写。 -
但是现在都19年了,新项目一般都支持了c++11了。
用模板包装单例
template< typename T>
class Singleton
{
public:
static T& getInstance() {
static T value_; //静态局部变量
return value_;
}
private:
Singleton();
~Singleton();
Singleton( const Singleton&); //拷贝构造函数
Singleton& operator=( const Singleton&); // =运算符重载
};
class A{
public:
A(){
a = 1;
}
void func(){
cout << "A.a = " << a << endl;
}
private:
int a;
};
class B{
public:
B(){
b = 2;
}
void func(){
cout << "B.b = " << b << endl;
}
private:
int b;
};
// 使用demo
int main()
{
Singleton<A>::getInstance().func();
Singleton<B>::getInstance().func();
return 0;
}
总结
单例模式虽然听起来简单,但是要考虑的方面非常多,例如:
性能 多线程 阻止拷贝构造和赋值 通用化
来源:编程珠玑