c++ 正确的单例模式

  • Post author:
  • Post category:其他




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); // 禁止移动、拷贝
};



版权声明:本文为weixin_40683787原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。