单例模式

在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。 单例模式的典型应用就是任务队列。

如果使用单例模式,首先要保证这个类的实例有且仅有一个。

  • 构造函数:创建一个新的对象
  • 拷贝构造函数:根据已有对象拷贝出一个新的对象
  • 拷贝赋值操作符重载函数:两个对象之间的赋值

为了把一个类可以实例化多个对象的路堵死,可以做如下处理:

  1. 构造函数私有化,在类内部只调用一次,这个是可控的。

    • 由于使用者在类外部不能使用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,我们都会把这个静态对象的访问权限设置为私有的。

    • 在类中只有它的静态成员函数才能访问其静态成员变量,所以可以给这个单例类提供一个静态函数用于得到这个静态的单例对象。

  2. 拷贝构造函数私有化或者禁用(使用 = delete

  3. 拷贝赋值操作符重载函数私有化或者禁用(从单例的语义上讲这个函数已经毫无意义,所以在类中不再提供这样一个函数,故将它也一并处理一下。

因此,定义一个单例模式的类的示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// 定义一个单例模式的类
class Singleton
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
Singleton(const Singleton& obj) = delete;
Singleton& operator=(const Singleton& obj) = delete;
static Singleton* getInstance();
private:
Singleton() = default;
static Singleton* m_obj;
};

在实现一个单例模式的类的时候,有两种处理模式:

  • 饿汉模式
  • 懒汉模式

1. 饿汉模式

饿汉模式就是在类加载的时候立刻进行实例化,这样就得到了一个唯一的可用对象。关于这个饿汉模式的类的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 饿汉模式
class TaskQueue
{
public:
// = delete 代表函数禁用, 也可以将其访问权限设置为私有
TaskQueue(const TaskQueue& obj) = delete;
TaskQueue& operator=(const TaskQueue& obj) = delete;
static TaskQueue* getInstance()
{
return m_taskQ;
}
private:
TaskQueue() = default;
static TaskQueue* m_taskQ;
};
// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

int main()
{
TaskQueue* obj = TaskQueue::getInstance();
}

在第17行,定义这个单例类的时候,就把这个静态的单例对象创建出来了。当使用者通过getInstance()获取这个单例对象的时候,它已经被准备好了。

注意事项:类的静态成员变量在使用之前必须在类的外部进行初始化才能使用。

2. 懒汉模式