单例模式(Singleton Pattern)是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。该类负责创建自己的对象,同时确保只有一个对象被创建。一般常用在工具类的实现或创建对象需要消耗资源的业务场景。
本文原创,代码和解释都由本人编写,如有发现错误或疏漏请指出,转载请注明出处(lance.moe)。
单例模式的特点
- 类构造器私有
- 持有自己类的引用
- 对外提供获取实例的静态方法
防拷贝构造
1
2
3
4
5
6
7
8
9
| class noncopyable {
protected:
noncopyable() = default;
~noncopyable() = default;
noncopyable(noncopyable &&) = delete; // Move construct
noncopyable(const noncopyable &) = delete; // Copy construct
noncopyable &operator=(const noncopyable &) = delete; // Copy assign
noncopyable &operator=(noncopyable &&) = delete; // Move assign
};
|
需要注意的是 C++ 11 开始增加了右值引用语法,很多较为老旧的库没有做移动构造语义的处理,这里已经加上。
单例模式
1
2
3
4
5
6
7
8
| template <typename T>
class singleton : public noncopyable {
public:
static T &instance() {
static T _instance {};
return _instance;
}
};
|
类内静态方法内的局部静态变量在 C++11 后可保证原子性和线程安全,所以只需要做防拷贝处理即可完成单例。将其制作成模板类,使用时只需要继承 singleton 即可。
实体类
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
| class Client final : public singleton<Client> {
friend class singleton<Client>;
protected:
Client();
~Client();
public:
static unsigned long version();
static void set_version(unsigned long ver);
protected:
unsigned long _version;
};
Client::Client() : _version(0) {
// pass
}
Client::~Client() {
// pass
}
unsigned long Client::version() {
return instance()._version;
}
void Client::set_version(unsigned long ver) {
instance()._version = ver;
}
|
- 一个单例模式类要加入 final 来限定不能被继承
- 要声明友元类,否则 singleton 基类无法获取实体类的构造函数
完整例子
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| #include <iostream>
class noncopyable {
protected:
noncopyable() = default;
~noncopyable() = default;
noncopyable(noncopyable &&) = delete; // Move construct
noncopyable(const noncopyable &) = delete; // Copy construct
noncopyable &operator=(const noncopyable &) = delete; // Copy assign
noncopyable &operator=(noncopyable &&) = delete; // Move assign
};
template <typename T>
class singleton : public noncopyable {
public:
static T &instance() {
static T _instance;
return _instance;
}
};
class Client final : public singleton<Client> {
friend class singleton<Client>;
protected:
Client();
~Client();
public:
static unsigned long version();
static void set_version(unsigned long ver);
protected:
unsigned long _version;
};
Client::Client() : _version(0) {
// pass
}
Client::~Client() {
// pass
}
unsigned long Client::version() {
return instance()._version;
}
void Client::set_version(unsigned long ver) {
instance()._version = ver;
}
int main() {
using namespace std;
Client::set_version(1919810ul);
cout << Client::version() << endl; // 输出: 1919810
const auto &client = Client::instance(); // 正确
noncopyable test1(); // 编译器报错,noncopyable 没有构造器不能被实体化
Client client(); // 编译器报错,Client 构造器被保护不能被实体化
Client *client = new Client(); // 编译器报错,同上
return 0;
}
|
不改造类的情况下使用单例(技巧)
在实际工程中,可能修改一个类的成本比较大,或者是一个基类不方便被修改,那么可以用如下方法使用单例。(只是作为一个技巧,个人不是很推荐,因为无法像上面类继承方案一样做禁止构造操作。很可能出现很多手滑的误用。)
1
2
3
4
5
| template <typename T>
T &use() {
static T _instance {};
return _instance;
}
|
这里我们利用模板来制作一个 use 函数。下面给一个用例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #include <iostream>
template <typename T>
T &use() {
static T _instance {};
return _instance;
}
class Foo {
public:
Foo() {
puts("I was constructed!");
}
void bar() {
puts("Called bar!");
}
};
int main() {
use<Foo>().bar();
use<Foo>().bar();
return 0;
}
|
输出结果:
1
2
3
| I was constructed!
Called bar!
Called bar!
|
可以看到,使用这种方法也可以保证该类只被初始化一次。(前提是不手滑…)
总结
单例模式是一种很基础的设计方式,在面向对象编程流行的时期,单例模式广受批评。
第一点,主要是单例模式不基于接口,对继承、多态不友好。
第二点,在 C++11 普及之前,没有一种能够简单高效靠谱的实现单例模式的方案(大部分方案多多少少都存在一些问题)。
不过现在已经是各门语言里函数式语法满天飞的年代了,在很多 JavaScript 语言的库中,例如 react,编写视图甚至已经开始大力推广FC(Function Components)来代替年事已高的CC(Class Components),时间也证明过度面向对象、过度抽象对一个项目来说反而会降低代码可读性,有些时候不一定是一件好事。
我个人不反对单例,当然这并不意味着单例模式应该被滥用,还是要针对具体情况来定。
以上是个人见解,欢迎补充。