单例和智能指针

单例

单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例。

  1. 单例类只能有一个实例。

    为此,单例类只能提供私有的构造函数,即保证不能随意创建该类的实例。

  2. 单例类必须自己创建自己的唯一实例。

    因为构造函数是私有的,其他对象不能创建单例类的实例,只能是单例类自己来创建。

  3. 单例类必须给所有其他对象提供这一实例。

    外界需要获取并使用这个单例类的实例,但是由于该类的构造函数是私有的,外界无法通过new去获取它的实例,那么就必须提供一个静态的公有方法,该方法创建或者获取它本身的静态私有对象并返回。

智能指针 shared_ptr

智能指针是存储动态分配对象指针的类,用于生命周期的控制。当指针离开其作用域时,自动销毁动态分配的空间,防止内存泄漏。

std::shared_ptr采用引用计数,每一个shared_ptr的拷贝都指向相同的内容,当最后一个shared_ptr析构的时候,内存被释放。

使用智能指针创建单例模板

为了减少重复代码,实现单例模板,需要创建单例的只需把这个模板添加为友元类即可

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
#ifndef SINGLETON_H
#define SINGLETON_H

#include <memory>
#include <mutex>

template <typename T>
class singleton
{
public:
template <typename... Args>
static T* get_instance(Args&&... args)
{
if (!s_instance)
{
std::lock_guard<std::mutex> lck(s_instance_mtx);
if (!s_instance)
{
s_instance = std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
}
return s_instance.get();
}

static void release_instance()
{
if (s_instance)
{
std::lock_guard<std::mutex> lck(s_instance_mtx);
if (s_instance)
{
s_instance.reset();
}
}
}

private:
static std::shared_ptr<T> s_instance;
static std::mutex s_instance_mtx;
};

template <typename T>
std::mutex singleton<T>::s_instance_mtx;

template <typename T>
std::shared_ptr<T> singleton<T>::s_instance;

#endif /* SINGLETON_H */

问题

这个单例已经在以前的项目中跑了一年多了,最近发现在第一次调用release_instance()时就会被析构,智能指针的引用计数器貌似没有发挥作用

经过LOG,和调查发现根本原因是在get_instance()时,返回的是原始指针,所以智能指针只有创建时的一次引用,即引用计数器永远为1

解决方法

  1. 第一个解决方法就是返回智能指针,要保证在使用单例的地方也声明对应的智能指针
  2. 第二个是我们自己添加一个计数器,get_instance()时计数器加一,release_instance()时减一,当计数器为0,就析构

两种修改方法都很简单,就不放修改代码了。


单例和智能指针
https://carl-5535.github.io/2022/03/07/工作总结/单例和智能指针/
作者
Carl Chen
发布于
2022年3月7日
许可协议