概述
单例模式的概念
单例模式指的是一个类在整个应用程序中只有一份实例。
注意:
- 1、单例类只有一份实例
- 2、单例类只能由该类自己创建一份实例。
- 3、单例类必须提供一个全局的访问点,以供类外获取到这份实例
为什么只能由该类自己创建一份实例 ?
假设有一个全局变量count, 它也是一个对象。如果在一个源文件中使用int count = 0
创建了一个对象,在源文件包含的头文件中也使用了int count = 0
创建了一个对象。这两个count并不是同一个对象!所以编译的时候会报重定义的错误。类对象的创建也是如此,所以单例类必须由它自己创建一份实例,那么类外就获取不到它的实例,这个类就必须提供一个全局的访问点,以供类外获取到这份实例。
单例模式的两种方式
饿汉模式:事先准备好,程序启动时就创建一个唯一的实例对象。不管以后用还是不用,需要的时候可以直接取用 。
饿汉模式的优点:简单
饿汉模式的缺点
- 无法控制单例创建初始化顺序
- 如果单例对象初始化很费时间,那么程序启动就会很慢
懒汉模式:事先没有准备好,需要的时候再做工作。
懒汉模式的优点
- 能够控制单例创建初始化的顺序
- 单例对象的初始化不会影响程序启动的快慢。
懒汉模式的缺点:相对于饿汉模式要复杂些,尤其是还要控制线程安全的问题。
懒汉模式代码速览
//饿汉模式的单例模式
class Singleton
{
public:
//2、提供一个可供全局的访问点,以获取到该实例
static Singleton& GetInstance()
{
cout << "调用一次GetInstance" << endl;
return _st;
}
private:
Singleton()
:_cap(0)
{
printf("Singleton()->%pn", &_cap);
}
//将拷贝封死,防止被类外用各种途径创建对象
Singleton(const Singleton& st) = delete;
Singleton& operator=(const Singleton& st) = delete;
private:
queue<int> _que;
int _cap;
static Singleton _st; //1、自己创建一个实例
};
Singleton Singleton::_st; //在程序入口前就完成对单例对象的初始化
饿汉模式编写逻辑
下面是一个类的框架,我们要把它改成饿汉式的单例模式
//饿汉模式的单例模式
class Singleton
{
public:
Singleton()
:_cap(0)
{}
private:
queue<int> _que;
int _cap;
};
那么首先就需要保证类外不能够创建对象,所以我们把类外,能够创建类对象的渠道封死
1、构造函数私有 2、封死拷贝构造函数 3、封死赋值拷贝函数
//饿汉模式的单例模式
class Singleton
{
public:
private:
Singleton()
:_cap(0)
{
printf("Singleton()->%pn", &_cap);
}
//将拷贝封死,防止被类外用各种途径创建对象
Singleton(const Singleton& st) = delete;
Singleton& operator=(const Singleton& st) = delete;
private:
queue<int> _que;
int _cap;
};
现在类外已经不能够创建对象了,由单例类自己创建一个实例:
//饿汉模式的单例模式
class Singleton
{
public:
private:
Singleton()
:_cap(0)
{
printf("Singleton()->%pn", &_cap);
}
//将拷贝封死,防止被类外用各种途径创建对象
Singleton(const Singleton& st) = delete;
Singleton& operator=(const Singleton& st) = delete;
private:
queue<int> _que;
int _cap;
static Singleton _st; //1、自己创建一个实例
};
Singleton Singleton::_st; //在程序入口前就完成对单例对象的初始化
现在类已经有实例了,需要给类外提供一个全局的访问点,以供类外获得这个对象。
```cpp
//饿汉模式的单例模式
class Singleton
{
public:
//2、提供一个可供全局的访问点,以获取到该实例
static Singleton& GetInstance()
{
cout << "调用一次GetInstance" << endl;
return _st;
}
private:
Singleton()
:_cap(0)
{
printf("Singleton()->%pn", &_cap);
}
//将拷贝封死,防止被类外用各种途径创建对象
Singleton(const Singleton& st) = delete;
Singleton& operator=(const Singleton& st) = delete;
private:
queue<int> _que;
int _cap;
static Singleton _st; //1、自己创建一个实例
};
Singleton Singleton::_st; //在程序入口前就完成对单例对象的初始化
懒汉模式代码速览
仅支持C++11标准的懒汉模式代码, 版本一:
//懒汉模式的单例模式
class Singleton
{
public:
static Singleton& GetInstance()
{
cout << "调用了一次GetInstance" << endl;
//double check
if (_sPtr == nullptr)
{
unique_lock<mutex> lock(_mtx);
if (_sPtr == nullptr)
{
_sPtr = new Singleton;
}
}
return *_sPtr;
}
private:
Singleton()
:_cap(0)
{
printf("Singleton()->%pn", &_cap);
}
//将拷贝封死,防止被类外用各种途径创建对象
Singleton(const Singleton& st) = delete;
Singleton& operator=(const Singleton& st) = delete;
private:
queue<int> _que;
int _cap;
static Singleton* _sPtr;
static mutex _mtx;
};
mutex Singleton::_mtx;
Singleton* Singleton::_sPtr = nullptr;
上面的这份版本只需要修改 封死类外创建类对象的途径 的方式, 就是C++98和C++11都支持的版本, 版本二
......
//将拷贝封死的方式更改为C++98支持的
Singleton(const Singleton& st);
Singleton& operator=(const Singleton& st);
......
懒汉模式的另一种优化版本, 版本三:
//懒汉模式的单例模式
class Singleton
{
public:
static Singleton& GetInstance()
{
cout << "调用了一次GetInstance" << endl;
static Singleton sInst;
return sInst;
}
private:
Singleton()
:_cap(0)
{
printf("Singleton()->%pn", &_cap);
}
//将拷贝封死,防止被类外用各种途径创建对象
Singleton(const Singleton& st) = delete;
Singleton& operator=(const Singleton& st) = delete;
private:
queue<int> _que;
int _cap;
};
C++98中,static修饰的对象构造初始化不能保证是线程安全的。而C++11对这个问题做了优化,C++11中被static修饰的对象构造初始化一定是线程安全的。所以才会有懒汉模式的版本三。
关于懒汉模式的双重检查
懒汉模式的单例提供了一个全局的访问点:
static Singleton& GetInstance()
{
cout << "调用了一次GetInstance" << endl;
//double check
if (_sPtr == nullptr)
{
unique_lock<mutex> lock(_mtx);
if (_sPtr == nullptr)
{
_sPtr = new Singleton;
}
}
return *_sPtr;
}
为什么不能是下面这个样子?
static Singleton& GetInstance()
{
cout << "调用了一次GetInstance" << endl;
if (_sPtr == nullptr)
{
_sPtr = new Singleton;
}
return *_sPtr;
}
在多线程下,它不是线程安全的,所以极大概率会造成下面的情况
因此需要加锁解锁,来保证线程安全。
static Singleton& GetInstance()
{
cout << "调用了一次GetInstance" << endl;
unique_lock<mutex> lock(_mtx);
if (_sPtr == nullptr)
{
_sPtr = new Singleton;
}
return *_sPtr;
}
实际上,只有第一次去创建的时候(_sPtr == nullptr)才会有线程安全的问题, 如果使用仅仅像上面这个样子,那么不管是否已经有实例,每一次调用都会涉及加锁和解锁,极大的降低了效率。因此采用双重检查的方式,即可以保证线程是安全的,也提升了效率。
static Singleton& GetInstance()
{
cout << "调用了一次GetInstance" << endl;
//double check
if (_sPtr == nullptr)
{
unique_lock<mutex> lock(_mtx);
if (_sPtr == nullptr)
{
_sPtr = new Singleton;
}
}
return *_sPtr;
}
最后
以上就是冷艳冥王星为你收集整理的23种常用设计模式之一:单例模式的全部内容,希望文章能够帮你解决23种常用设计模式之一:单例模式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复