本文介绍了一种通过C++实现简单智能指针的方式。智能指针是一种利用对象管理资源的方法,可以保证资源在不需要时自动释放,避免了内存的泄漏。这里实现的智能指针内部包含了两个指针,一个指向管理的对象资源,另一个指向引用计数模块。
实现代码:https://github.com/zpye/cxx_tools/blob/master/tools/include/SharedPtr.h
1. 引用计数模块
这个模块的主要作用是记录当前共有多少智能指针正在引用资源,当计数变为0时模块会释放资源。
(1) 原子计数
使用原子操作来进行计数的目的是实现多线程下的正确性。程序中使用std::atomic_size_t
定义计数用的原子变量,初始化时设为1,并根据引用的变化情况进行增减操作。当计数减少到0时,模块会释放存储的对象以及自身。
(2) 模板类型
为了能够存储和释放任意类型,将引用计数模块设计成模板类。考虑到指针即有可能指向单个对象,也有可能指向对象数组,设计了一个非模板的基类,在继承时添加模板以及数组类型的偏特化,其内部的主要区别就是释放对象时使用了delete
还是delete[]
。
2. 智能指针
智能指针SharedPtr<T>
使用了模板类实现,模板类型表示指针所指对象的类型。
(1) 初始化
首先实现了默认构造函数SharedPtr()
和参数为空指针时的构造函数SharedPtr(std::nullptr_t)
,功能就是把两个指针都置为空,等待之后赋值或者析构。
最常用的构造函数SharedPtr(U* ptr)
接收一个指针作为参数,该指针通常为初始化资源时(new
)得到的。考虑到传递给参数的指针类型U
可能与智能指针的模板类型T
不一致,因此将该构造函数用模板函数实现。此处的类型U
为传入指针的类型,并通过enable_if
的手段来判断传入指针的类型能否转化为智能指针的类型T
。
构造函数的内部了另一个函数SetRef
来进行真正的初始化操作,这样做的原因是考虑到传入的指针即有可能是单个对象,也有可能是数组。将std::is_array<T>::type{}
作为函数的参数并分别使用std::true_type
和std::false_type
对函数进行重载就可以创建正确的引用计数模块。
(2) 复制构造和移动构造
智能指针的复制构造不会申请新的对象资源,而是使用类似于浅复制的方法只复制指向对象资源和引用计数模块的两个指针,并且把计数增加1。
移动构造则是类似于交换的操作,将作为参数的智能指针对象的两个指针值赋予构造的智能指针,然后将参数的两个指针置为空,防止析构时减少引用计数。
(3) 交换、赋值运算、重置
交换操作Swap(SharedPtr& sp)
接收一个相同类型的智能指针作为引用形参,内部的主要工作就是交换两个智能指针内部的对象资源指针和引用计数模块指针。
赋值运算符operator=
的重载利用了交换操作,利用传入的智能指针构造了一个临时变量,并将这个临时变量与自身*this
交换,结束后临时变量自动释放。
重置Reset(U* ptr)
接受的参数与构造函数相同,其实现也用到了交换操作,先构造一个临时变量,再与自身*this
交换,结束时释放临时变量。
(4) 获取保存内容
获取智能指针管理的对象资源基本上就是操作指向对象的指针,Get()
方法会返回指针的值,重载操作符operator->()
的实现也是返回指针的值,在实际使用时可以像使用普通指针一样使用智能指针,重载操作符operator[](int i)
针对数组类型,可以指定获取数组中的某一个对象。
参考资料
- Effective C++ 3rd Edition
- More Effective C++
- https://en.cppreference.com/w/cpp/memory/shared_ptr
- Visual Studio中shared_ptr的相关代码实现