1.单例模式
单例模式是最最常见的一种模式,平时开发过程中用的也挺多的,目的是:
只有一个实例,这个实例可以只持有一份全局共用的数据,也可以是仅持有一堆接口,等等,能用上单例模式的很多。
最近在检视别人和自己的代码过程中,发现了一些常见的错误/问题,在此处列举一下
2.单例模式规范
2.1 单例模式的接口类型:
1.返回引用型
Singleton &Singleton::GetInstance()
{
static Singleton instance;
return instance;
}
2.返回指针型
Singleton *Singleton::GetInstance()
{
static Singleton instance;
return &instance;
}
常见错误:
对于c++初学者/刚切c++语言的使用者来说,可能引用/指针的概念不太清晰,可能写成了:
Singleton Singleton::GetInstance()
{
static Singleton instance;
return instance;
}
这样子是达不到单例效果的,每次都会产生一拷贝构造
个人习惯用引用的方式,后面就以引用为例子
2.2 单例模式的构造函数必须显式声明为非public(最好是private)
众所周知。如果构造函数是public的话,就达不到单例的目的了
常见错误: 忘记显式声明(一般没有傻到显式声明构造函数还用单例的吧)
class Singleton {
public:
static Singleton &GetInstance()
{
static Singleton instance;
return instance;
}
};
因为没有声明,所以别人可以在外部创建
2.3 单例模式必须禁用拷贝构造、拷贝赋值、移动构造、移动赋值
曾经检视过一个同事的代码,单例写成了
class Singleton {
public:
static Singleton &GetInstance()
{
static Singleton instance;
return instance;
}
private:
Singleton() = default; // 构造函数显式声明为private
};
void Func() {
auto instance = Singleton::GetInstance();
}
注:
auto不会自动推导出引用
所以这里做的事情等价于:
auto &instanceRef = Singleton::GetInstance();
auto instance = instanceRef;
这里产生了一次拷贝构造
如何从代码的角度完全避免这种行为呢?
就需要将所有的拷贝、移动相关的都显式禁止
正确做法如下:
#define DISABLE_COPY_AND_MOVE(_CLASS_NAME) \
_CLASS_NAME(const _CLASS_NAME&) = delete; \
_CLASS_NAME& operator=(const _CLASS_NAME &) = delete; \
_CLASS_NAME(_CLASS_NAME &&) = delete; \
_CLASS_NAME& operator=(_CLASS_NAME &&) = delete;
class Singleton {
public:
static Singleton &GetInstance()
{
static Singleton instance;
return instance;
}
private:
Singleton() = default; // 构造函数显式声明为private
DISABLE_COPY_AND_MOVE(Singleton);
};
void Func() {
auto instance = Singleton::GetInstance();
}
DISABLE_COPY_AND_MOVE 将上述四个都显式删除了
这时候会发现
auto instance = Singleton::GetInstance();
编译不过
通过这种方式可以保证单例不会发生任何的拷贝或移动
即别人写出
std::move / auto instance = …之类的行为,都是不被允许的
2.3 单例类的析构必须显式声明为private
#define DISABLE_COPY_AND_MOVE(_CLASS_NAME) \
_CLASS_NAME(const _CLASS_NAME&) = delete; \
_CLASS_NAME& operator=(const _CLASS_NAME &) = delete; \
_CLASS_NAME(_CLASS_NAME &&) = delete; \
_CLASS_NAME& operator=(_CLASS_NAME &&) = delete;
class Singleton {
public:
static Singleton &GetInstance()
{
static Singleton instance;
return instance;
}
private:
Singleton() = default; // 构造函数显式声明为private
DISABLE_COPY_AND_MOVE(Singleton);
};
void Func() {
auto &instance = Singleton::GetInstance();
auto ptr = &instance;
delete ptr;
}
假如代码使用场景中,通过某些方式持有到了单例的指针,(尤其是unique_ptr),那么这个指针在析构时,就会将指针释放
如上面的写法,很恶意的delete了这个指针,也是可以的。
删除指针后,别人拿到的单例的引用/指针,就会导致程序崩溃
正确做法:
#define DISABLE_COPY_AND_MOVE(_CLASS_NAME) \
_CLASS_NAME(const _CLASS_NAME&) = delete; \
_CLASS_NAME& operator=(const _CLASS_NAME &) = delete; \
_CLASS_NAME(_CLASS_NAME &&) = delete; \
_CLASS_NAME& operator=(_CLASS_NAME &&) = delete;
class Singleton {
public:
static Singleton &GetInstance()
{
static Singleton instance;
return instance;
}
private:
Singleton() = default; // 构造函数显式声明为private
~Singleton() = default; // 析构函数显式声明为private
DISABLE_COPY_AND_MOVE(Singleton); // 禁止移动、拷贝
};