智能指针原理
智能指针基本上就是利用 RAII 技术实现的。资源取得时机便是初始化时机(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术。在对象构造时获取资源,接着控制对资源的访问,使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
智能指针的实现要考虑的问题:
- 怎么实现 RAII
- 如何重载 operator* 和 opertaor->
- 怎么复制智能指针对象
前两个问题比较简单,本文就不多做赘述了。下文主要介绍如何正确实现 shared_ptr 的复制函数。
shared_ptr
shared_ptr 这种智能指针访问对象采用共享所有权来管理其生存期。没有哪个特定的 shared_ptr 拥有该对象。取而代之的是,所有指涉到它的 shared_ptr 共同协作,确保在不再需要该对象的时刻将其析构。当最后一个指涉到某对象的 shared_ptr 不再指涉到它时,该 shared_ptr 会析构其指涉到的对象。
因此需要知道有多少个 shared_ptr 指向同一个资源。我们可以用一个引用计数,该计数在构造的时候初始化,复制的时候只需要将该值加 1 即可。经复制得到的对象共享同一个引用计数。
用 static int 实现引用计数可以吗?
既然我们需要多个对象共享同一个引用计数,那使用静态变量可以吗?
这是不行的,使用静态变量是可以做到共享,但静态变量在整个类中只有一个。也就是说,我们定义出的所有对象,共享同一个计数,这明显是不行的。
用 int 实现引用计数可以吗?
很明显不可以。当我们用复制一个 shared_ptr 类型对象时,我们获取到的是 int 类型的值,两个对象没有任何关联。
那用指针实现引用计数可以吗?
这是可以的。
假如我们用 int* 实现计数。在构造函数中,我们为指针 new 一块空间,并初始化为 1,表示有一个 shared_ptr 指向该对象。在复制构造函数中,我们直接复制指针的值,这样两个指针指向的实际是同一块空间,将指向的空间的值加 1 即可。
上面这样在多线程情况下会有问题,因此我们需要用一些方式来保护对临界资源的访问。下面分别用互斥锁和原子变量两种方式实现。
互斥锁
#include <iostream>
#include <mutex>
using namespace std;template<class T>
class SharedPtr {public:SharedPtr(T* ptr) : _data(ptr),_cnt(new int(1)),_mtx(new mutex) {cout << "构造函数" << endl;} ~SharedPtr() {bool deleteFlag = false;_mtx->lock();if ((*_cnt)-- == 1) {delete _data;delete _cnt;deleteFlag = true;}_mtx->unlock();if (deleteFlag) {cout << "析构函数" << endl;delete _mtx;}}SharedPtr(const SharedPtr<T>& sp) : _data(sp._data),_cnt(sp._cnt),_mtx(sp._mtx) {_mtx->lock();(*_cnt)++;_mtx->unlock();}SharedPtr<T>& operator=(const SharedPtr<T>& sp) {SharedPtr<T> tmp = sp;Swap(tmp);return *this;}private:void Swap(SharedPtr<T>& other) {std::swap(_data, other._data);std::swap(_cnt, other._cnt);std::swap(_mtx, other._mtx);}private:T* _data;int* _cnt;mutex* _mtx;
};int main() {SharedPtr<int> sp1(new int(10));SharedPtr<int> sp2(sp1);SharedPtr<int> sp3(new int(1));sp3 = sp2;return 0;
}
原子变量
在上面的实现方式中,我们用了互斥锁来保护计数,以实现原子性的加减。我们也可以直接用 C++ 提供的原子类型变量。
#include <atomic>
#include <iostream>
using namespace std;class RefCnt {public:RefCnt(): _cnt(1) {}int AddRef() {return ++_cnt;}int SubRef() {return --_cnt;}private:atomic<int> _cnt;
};template<class T>
class SharedPtr {public:SharedPtr(T* ptr) : _data(ptr),_refCnt(new RefCnt) {cout << "构造函数" << endl;}~SharedPtr() {if (_refCnt->SubRef() == 0) {delete _data;delete _refCnt;cout << "析构函数" << endl;}}SharedPtr(const SharedPtr<T>& sp) : _data(sp._data),_refCnt(sp._refCnt) {_refCnt->AddRef();}SharedPtr<T>& operator=(const SharedPtr<T>& sp) {SharedPtr<T> tmp = sp;Swap(tmp);return *this;}private:void Swap(SharedPtr<T>& other) {std::swap(_data, other._data);std::swap(_refCnt, other._refCnt);}private:T* _data;RefCnt* _refCnt;
};int main() {SharedPtr<int> sp1(new int(10));SharedPtr<int> sp2(sp1);SharedPtr<int> sp3(new int(1));sp3 = sp2;return 0;
}
代码比起使用互斥锁的方法简单了不少。