智能指针
文章目录
背景:
- 内存泄漏,自动释放。栈上创建的智能指针指向堆上创建的对象。
- 共享所有权指针的传播和释放
方法:
p.get() // 获取原始指针的值
/* 问题:
不能保存get()的返回值 -> 空悬指针
不能delet get()的返回值,delete两次 */
p.reset(...) // 重置指针
/*
无参数 -> p指针置空
有参数 -> p指向...对象,并释放 p (delete) 原来的空间
注:对share_ptr来说,若p指针不是唯一指向该对象的指针,delte操作只减少其引用计数 */
智能指针的类型
1. auto_ptr
已经弃用,语法形式上表达的是值语义,但底层实现时已经发生了所有权的转移
2. unique_ptr
-
独享所有权的智能指针
unique_ptr<Point> up(new Point); auto up(std::make_unique<Point>()); // C++14后才有 /* 注:new 重复了被创建对象的键入,但是make_unique函数则没有。工程中应当避免代码重复造成引起编译次数增加。 */
-
不能表达对象语义,即不能进行复制控制
unique_ptr<Point> up2(up1); // error up3 = up; // error
-
具有移动语义,可以作为容器元素
// 1、unique_ptr 具有移动语义的函数,转换成右值后托管的资源变成了空指针 unique_ptr<Point> up(new Point(1, 2)); up2 = std::move(up); // up2->up,up->nullptr // 2、unique_ptr 作为容器元素 unique_ptr<Point> up(new Point(1, 2)); // 左值 vector<unique_ptr<Point>> points; // 容器中存放的是智能指针 // points.push_back(up); // error,传递左值,调用拷贝构造函数,表达对象语义错误 points.push_back(std::move(up)); // 正确,右值,调用移动赋值运算符函数
-
指定删除器(需要指定删除器的类型),回收资源
// unique_ptr可以指向一个数组,shared_ptr C++17后支持 unique_ptr<int []> ptr(new int[10]); // C++11支持 shared_ptr<int []> ptr2(new int[10]); // C++17支持, // unique_ptr需要确定删除器的类型 // unique_ptr<int> ptr(new int(1), [](int *p){delete p;}); //error unique_ptr<int, void(*)(int*)> ptr(new int(1), [](int *p){delete p;});
3. shared_ptr
share_ptr 原理
shared_ptr 包含两个指针,一个指向堆上创建的对象的裸指针,另一个指向控制块。
share_ptr 特点
-
共享所有权的智能指针
// 初始化 shared_ptr<int> sp = make_shared<int>(100); // 推荐,原因同上 shared_ptr<int> p1(new int(1));
-
强引用的智能指针
-
可以进行复制控制,通过引用计数来完成。当复制控制时,引用计数+1。当被销毁时,先将引用计数减1;再去判断引用计数是否为0;如果为0, 才真正释放资源。通过
use_count
获取引用计数。 -
可以进行转移操作
-
指定删除器,回收资源
std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});
share_ptr 常用方法
s.get():// 返回shared_ptr中保存的裸指针;
s.reset(...):// 重置shared_ptr
s.use_count() // 返回shared_ptr的强引用计数;
s.unique() :// use_count()为1,返回true,否则返回false。
share_ptr 问题
-
不能用一个原始指针初始化多个 shared_ptr
int *ptr = new int; shared_ptr<int> p1(ptr); shared_ptr<int> p2(ptr); // 逻辑错误,其他share_ptr不知道共享对象
-
不要在函数实参中创建 shared_ptr,函数创建失败,但内存已分配
-
通过
shared_from_this()
返回 this 指针不要直接用 shared_ptr 返回 this 指针, this 指针本质上是一个裸指针,可能会重复析构。正确返回 this 的 shared_ptr 的方法是:让目标类通过继承
std::enable_shared_from_this
类,然后使用基类的成员函数shared_from_this()
来返回 this 的 shared_ptr// 1、让目标类通过继承 std::enable_shared_from_this 类 class A: public std::enable_shared_from_this<A> { public: shared_ptr<A>GetSelf() { // 2、使用基类成员函数 shared_from_this() 来返回 this 的 shared_ptr return shared_from_this(); } ~A() { cout << "Destructor A" << endl; } }; int main() { shared_ptr<A> sp1(new A); shared_ptr<A> sp2 = sp1->GetSelf(); // ok,若直接使用this指析构两次 return 0; } // 原理:学完 weak_ptr 再来 /* 原理:std::enable_shared_from_this 类中有一个weak_ptr,用来观察this智能指针。调用shared_from_this() 方法会调用内部这个weak_ptr的lock()方法返回观察的shared_ptr,其引用计数并不发生改变,不会重复析构。 注:获取自身智能指针的函数仅在 shared_ptr 的构造函数被调用之后才能使用,因为 enable_shared_from_this 内部的 weak_ptr 只有通过 shared_ptr 才能构造。 */
-
循环引用问题:两个
shared_ptr
互指,导致引用计数增加,不能靠对象的销毁使得引用计数变为0,从而导致内存泄漏。解决方法:将两个类的任一成员变量改为weakptr
class Child; class Parent { public: ~Parent() { cout << "~Parent()" << endl; } shared_ptr<Child> pChild; // error,正确应为:weak_ptr<Child> pChild; }; class Child { public: ~Child() { cout << "~Child()" << endl; } shared_ptr<Parent> pParent; // error,正确应为:weak_ptr<Parent> pParent; }; int main(void) { { shared_ptr<Child> child(new Child); shared_ptr<Parent> parent(new Parent); child->pParent = parent; parent->pChild = child; cout << "child的引用计数:" << child.use_count() << endl; //2 cout << "parent的引用计数:" << parent.use_count() << endl; //2 } // 问题:循环引用:退出了作用域,但是没有调用析构 cout << "main leave" << endl; return 0; }
-
安全问题:引用计数本身是安全的,修改指向对象的数据,不安全(加锁)
4. weak_ptr
弱引用指针,了解决 shared_ptr 循环引用的问题而提出来的。不控制对象生命周期,引用计数保持不变。
weak_ptr 特点
- weak_ptr 不能直接托管资源,只能通过 shared_ptr 复制过来
- 引用计数保持不变。
- 特殊:weak_ptr 没有提供访问资源 的方法,没有实现
opeartor*/->
- 要访问资源,必须要先通过
lock
函数提升为 shared_ptr 才可以访问。 - weak_ptr 知道所托管的资源是否还存活
weak_ptr 方法
use_count() // 获取当前观察资源的引用计数
expired() // 判断所观察资源是否已经释放
lock() // 获取监视的 shared_ptr
weak_ptr 使用
weak_ptr<int> wp;
{
shared_ptr<int> sp(new int(1)); //sp.use_count()==1
wp = sp; // wp不会改变引用计数,sp.use_count()==1
// weak_ptr不提供访问资源的方法。
// 如果要访问资源,只能调用lock方法将weak_ptr提升成shared_ptr
shared_ptr<int> sp2 = wp.lock();
}
// weak_ptr在使用前需要expired()函数判断资源是否存在
if(wp.expired()) {
cout << "shared_ptr is destroy" << endl;
}
else {
cout << "shared_ptr no destroy" << endl;
}
5、删除器的使用
智能指针默认使用 delete 来释放空间。若采用 malloc 申请的空间或是用 fopen 打开的文件,智能指针无法处理,因此我们需要为智能指针定制删除器。
自定义智能指针的方式有两种,函数指针与仿函数(函数对象)
struct FILECloser {
void operator()(FILE *fp) {
if(fp) {
fclose(fp);
cout << "fclose(fp)" << endl;
}
}
};
shared_ptr<FILE> up(fopen("wuhan1.txt", "a+"), FILECloser());