个人主页:仍有未知等待探索-CSDN博客
专题分栏:C++
一、概述
智能指针在构造的时候开辟空间,当智能指针生命周期结束则会自动调用析构函数释放空间。
解决问题:对于new开辟的时候出现异常,导致之前开辟的空间没有手动释放,内存泄露。
头文件:<memory>
智能指针有三种:auto_ptr,unique_ptr,shared_ptr。
二、理解
1、RAII
一种用对象生命周期来控制程序资源的技术。
在构造对象的时候获取资源,在析构对象的时候释放资源。
2、智能指针的原理
- RAII技术
- 重载 * 和 -> 运算符
不同智能指针之前的区别:
特点 auto_ptr
unique_ptr
shared_ptr
所有权 独占,可转移 独享,不可复制但可移动 共享,通过引用计数 数组管理 不支持 支持(通过特化版本) 支持(通过特化版本) 安全性 较低,已被弃用 较高,推荐使用 较高,但需注意循环引用 线程安全 不适用(已被弃用) 依赖于具体实现,但通常不是线程安全的(对对象的访问需要同步) 引用计数操作是线程安全的,但对象访问需要同步
3、auto_ptr
auto_ptr:管理权转移,被拷贝对象悬空,有风险。
不建议用!!!
auto_ptr<int> sp1(new int(1)); auto_ptr<int[]> ap2(new int[3]); // 报错,不支持数组管理 auto_ptr<int> sp2(sp1) // 转移资源,而不是拷贝构造 // 有坑 // sp1是左值,但是移动资源了
4、unique_ptr
- 不支持拷贝,支持数组管理。
- 线程一般不安全。
//auto_ptr<int> ap1(new int(3)); //auto_ptr<int[]> ap2(new int[3]); // 支持数组管理 unique_ptr<int[]> up1(new int[3]); // 普通创建 unique_ptr<int> up2(new int(1)); unique_ptr<A> up3(new A()); // 不支持拷贝和赋值 unique_ptr<A> up4(up3); // 支持移动拷贝和移动赋值 unique_ptr<A> up5(move(up3));
5、shared_ptr
- 不支持拷贝,支持数组管理。
- 引用计数操作是线程安全的,但是对对象访问需要同步。
注意:
1、构造智能指针建议用make_shared,这样的话会减少内存碎片化。
2、两个线程拷贝只能指针要++计数、智能指针对象析构也要--计数。所以要保证引用计数的线程安全。
3、智能指针对象本身拷贝析构是线程安全的,底层引用计数加减是线程安全的,指向的资源访问不是线程安全的。
缺陷:
1、循环引用
shared_ptr的缺陷:循环引用。--- 释放逻辑是个死循环。如下是循环引用的场景。
对p1的析构依赖于p2,对于p2的析构依赖于p1。
struct node { shared_ptr<node> prev; shared_ptr<node> next; ~node() { cout << "~node" << endl; } }; int main() { shared_ptr<node> p1(new node); shared_ptr<node> p2(new node); p1->next = p2; p2->prev = p1; return 0; }
这样写就没有循环引用的问题了。
struct node { weak_ptr<node> prev; weak_ptr<node> next; ~node() { cout << "~node" << endl; } }; int main() { shared_ptr<node> p1(new node); shared_ptr<node> p2(new node); p1->next = p2; p2->prev = p1; return 0; }
2、析构出错
当shared_ptr指向的是malloc开辟的空间,析构的时候就会出错。原因是shared_ptr底层释放空间是用的delete。
shared_ptr<A[]> p(new A[10]); -------------------------------------------- 遇到下面开辟空间方式的话,底层的delete可能会崩溃, 所以要有一个定制删除器。 shared_ptr<int> p((int*)malloc(4)); shared_ptr<FILE> p(fopen("test.txt","r"'));
所以需要用到一个定制删除器。
shared_ptr<int> p((int*)malloc(4), [](A*ptr){free(ptr);}); shared_ptr<FILE> p(fopen("test.txt","r"'), [](FILE* ptr){fclose(ptr);}
6、weak_ptr
weak_ptr的出现主要是为了解决shared_ptr循环引用的问题。
解决方案:
- weak_ptr不支持RAII,不单独管理。
- 赋值、拷贝不增加计数引用的次数,只指向资源。
expired函数:通过查看技术引用是否为零,来判断weak_ptr是否过期。
7、定制删除器
定制删除器也是为了解决shared_ptr在析构时候的缺陷。
我们需要在shared_ptr构造的时候传入一个定制删除器即可。
定制删除器:是一个可调用对象(函数指针、仿函数对象、lambda表达式)。
三、模拟实现shared_ptr
#include <iostream>
#include <functional>
#include <mutex>
using namespace std;
template<class T>
class SharedPtr
{
private:
T* _p;
int* _refcount;
mutex* _mtx;
function<void(T*)> _del;
public:
SharedPtr(T* p = nullptr)
:_p(p)
,_refcount(new int(1))
,_mtx(new mutex)
{}
template <class D>
SharedPtr(T* p = nullptr, D del = [](T* ptr) {delete ptr; })
: _p(p)
, _refcount(new int(1))
, _mtx(new mutex)
, _del(del)
{}
SharedPtr(const SharedPtr& p)
:_p(p._p)
,_refcount(p._refcount)
,_mtx(p._mtx)
{
add_refcount();
}
~SharedPtr()
{
release();
}
SharedPtr& operator=(SharedPtr& p)
{
if (&p == this) return *this;
release();
_p = p._p;
_refcount = p._refcount;
add_refcount();
return *this;
}
T& operator*()
{
return *_p;
}
T* operator->()
{
return _p;
}
int use_count()
{
return *_refcount;
}
private:
void add_refcount()
{
lock_guard<mutex> lg(*_mtx);
(*_refcount)++;
}
void release()
{
lock_guard<mutex> lg(*_mtx);
(*_refcount)--;
if (0 == _refcount)
{
_del(_p);
delete _refcount;
}
}
};
struct A
{
int _a;
};
int main()
{
/*SharedPtr<int> sp1(new int(1));
SharedPtr<int> sp2(sp1);
SharedPtr<int> sp3;
sp3 = sp2;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
cout << sp3.use_count() << endl;*/
SharedPtr<A> sp1((A*)malloc(sizeof(A)), [](A* ptr) { free(ptr); });
return 0;
}
谢谢支持!!!