来源:https://www.lance.moe/archives/post-356/
单例模式的特点
- 类构造器私有
- 持有自己类的引用
- 对外提供获取实例的静态方法
防拷贝构造
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;
}
};
类内静态方法内的局部静态变量在 C++11 后可保证原子性和线程安全,所以只需要做防拷贝处理即可完成单例。将其制作成模板类,使用时只需要继承 singleton 即可。
实体类
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 基类无法获取实体类的构造函数
完整例子
#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;
}
不改造类的情况下使用单例(技巧)
在实际工程中,可能修改一个类的成本比较大,或者是一个基类不方便被修改,那么可以用如下方法使用单例。(只是作为一个技巧,个人不是很推荐,因为无法像上面类继承方案一样做禁止构造操作。很可能出现很多手滑的误用。)
template <typename T>
T &use() {
static T _instance {};
return _instance;
}
这里我们利用模板来制作一个 use 函数。下面给一个用例:
#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;
}
总结
单例模式是一种很基础的设计方式,在面向对象编程流行的时期,单例模式广受批评。
第一点,主要是单例模式不基于接口,对继承、多态不友好。
第二点,在 C++11 普及之前,没有一种能够简单高效靠谱的实现单例模式的方案(大部分方案多多少少都存在一些问题)。
不过现在已经是各门语言里函数式语法满天飞的年代了,在很多 JavaScript 语言的库中,例如 react,编写视图甚至已经开始大力推广FC(Function Components)来代替年事已高的CC(Class Components),时间也证明过度面向对象、过度抽象对一个项目来说反而会降低代码可读性,有些时候不一定是一件好事。
我个人不反对单例,当然这并不意味着单例模式应该被滥用,还是要针对具体情况来定。