首先理解什么是RAII,RAII(Resource Acquisition Is Initialization)是C++中用来管理资源的生命周期的一种技术。
在 RAII 中,资源的获取和释放是在对象的构造函数和析构函数中完成的。当对象被创建时,它的构造函数被调用,从而获取资源;当对象超出作用域时,其析构函数被调用,从而释放资源。这样就确保了资源在对象的生命周期内始终是有效的,即使在发生异常或其他错误的情况下也能正确地释放资源。
不带引用计数的智能指针
下面实现一个非常简单的智能指针
#include <iostream>
#include <stdexcept>
template <typename T>
class SmartPointer
{
public:
SmartPointer(T *ptr) : _ptr(ptr) {}
~SmartPointer()
{
delete _ptr;
}
T *operator->() { return _ptr; }
T &operator*() { return *_ptr; }
private:
T *_ptr;
};
class Bar
{
public:
~Bar() { std::cout << "destructor" << std::endl; }
void func() { std::cout << "func()" << std::endl; }
};
int main()
{
{
SmartPointer<Bar> f(new Bar());
f->func();
(*f).func();
}
}
代码的运行结果如下:
func()
func()
destructor
可以看到,这个智能指针非常简单,就是在析构函数中正确释放资源,然后提供了->
和*
运算符重载函数。
智能指针就是对普通指针进行了一层封装,然后提供了对应的运算符重载函数,使其接口用起来像普通指针,同时在智能指针对象出作用域时会自动调用析构函数。
只要保证析构函数中,正确释放了资源,那么就能保证资源在出作用域时自动释放。
一般不将智能指针创建在堆上,创建在堆上
SmartPointer<Bar> *f = new SmartPointer<Bar>(new Bar());
,仍需要手动delete,才能访问智能指针的析构函数从而释放资源。这种做法与直接使用裸指针无异。
带引用计数的智能指针
上面的版本,不带引用计数,所以有一个很严重的问题。
int main()
{
{
SmartPointer<Bar> sp(new Bar());
SmartPointer<Bar> sp2(sp); // 拷贝构造
}
}
上面这个代码片段的执行结果为:
destructor
destructor
free(): double free detected in tcache 2
Aborted (core dumped)
可以发现,程序对同一个内存执行了两次delete。这是因为,SmartPointer<Bar> sp2(sp);
执行的是浅拷贝,故两个智能指针对象中存放的是同一个内存地址,所以会有double free。
那么,能不能把这个浅拷贝改成深拷贝呢?
答案显然是不能的,改成深拷贝意味着每次执行拷贝都会重新开辟内存空间,然后拷贝数据,但本来的意图可能只是需要传递一个指针而已,也就是两个指针指向同一个内存。
那么,如何解决这个问题呢?从是否共享所有权的角度来分类,有两种办法:
- 不共享所有权:
- 拒绝拷贝的发生,直接把拷贝(移动)构造,拷贝(移动)赋值函数删除,对应的是
std::scoped_ptr
。 - 每次拷贝都是移动,都把资源的所有权转移出去,也就是只有最后一个智能指针拥有所有权,其它智能指针中都置为nullptr,对应的是
std::auto_ptr
。 - 允许移动构造、移动赋值操作,不允许拷贝构造、拷贝赋值,也就是
c++11
中的std::unique_ptr
,通过std::unique_ptr<T> uptr2(std::move(uptr1))
来创建新对象。
- 拒绝拷贝的发生,直接把拷贝(移动)构造,拷贝(移动)赋值函数删除,对应的是
std::scoped_ptr
只能用于单个对象,不能拷贝移动,std::auto_ptr
自动转移所有权,容易出错,比如再次使用交出所有权的对象,这俩个都不推荐使用。
std::unique_ptr
可以移动构造新对象,而且是显式的发生所有权的转移的。
- 共享所有权:
利用引用计数,通过引用计数是否为0,来判断是否真的执行delete操作。当发生拷贝时,引用计数加一,当有一个智能指针出作用域,对应的引用计数减一,当引用计数减为0时,才真正执行delete操作。
这样就能保证,不发生double free,同时最后一个出作用域的智能指针能够正确释放资源。
尝试实现一个带引用技术的智能指针
#include <iostream>
#include <stdexcept>
template <typename T>
class RefCnt
{
public:
RefCnt(T *ptr) : ptr(ptr)
{
if (ptr)
cnt = 1;
}
void increRef()
{
cnt++;
}
int decreRef()
{
return --cnt;
}
private:
T *ptr;
int cnt; // 不是线程安全的
};
template <typename T>
class SharedPtr
{
public:
SharedPtr(T *ptr) : ptr(ptr)
{
rc = new RefCnt<T>(ptr);
}
SharedPtr(const SharedPtr &sp) : ptr(sp.ptr), rc(sp.rc)
{
if (ptr)
rc->increRef();
}
SharedPtr &operator=(const SharedPtr &sp)
{
if (this == &sp)
return *this;
if (0 == rc->decreRef())
{
delete ptr;
}
ptr = sp.ptr;
rc = sp.rc;
rc->increRef();
return *this;
}
~SharedPtr()
{
if (0 == rc->decreRef())
{
delete ptr;
delete rc;
}
}
T *operator->()
{
return ptr;
}
T &operator*()
{
return *ptr;
}
private:
T *ptr;
RefCnt<T> *rc;
};
int main()
{
SharedPtr<int> sp1(new int(10));
SharedPtr<int> sp2(sp1);
SharedPtr<int> sp3(nullptr);
sp3 = sp2;
*sp1 = 42;
std::cout << *sp2 << " " << *sp3 << std::endl;
}
标签:std,SharedPtr,智能,rc,拷贝,ptr,指针
From: https://www.cnblogs.com/ericling0529/p/18156564