c++ 正确的单例模式

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); // 禁止移动、拷贝
};
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值