四、智能指针与容器

当把shared_ptr对象放入一个容器中时,会调用shared_ptr的拷贝构造函数并且引用计数+1

int main(int argc, char const *argv[])
{shared_ptr<test> sp=make_shared<test>();vector<shared_ptr<test>> vsp;vsp.push_back(sp);cout<<sp.use_count()<<endl;return 0;
}

而当把weak_ptr放入一个容器中时,weak_ptr的引用计数不会增加,而且容器中的weak_ptr有可能是无效的,因为绑定的shared_ptr指向的对象有可能已经被释放

如果存在一个含有unique_ptr的容器,当向容器中添加元素时,要使用move函数转移unique_ptr的控制权,才能添加成功,因为unique_ptr的拷贝构造函数是delete的

五、智能指针与new

除了使用make_unique和make_shared来初始化unique_ptr和shared_ptr外,还可以使用new来初始化,因为new返回的是一个普通指针,而unique_ptr和shared_ptr的用普通指针初始化的构造函数是explicit的,所以,不能将一个普通指针隐式转化为智能指针,必须使用直接初始化的方式

template <class U>
explicit shared_ptr (U* p);explicit unique_ptr (pointer p) noexcept;

关于explicit,见博客https://blog.csdn.net/Master_Cui/article/details/106885137

示例

int main(int argc, char const *argv[])
{unique_ptr<test> up=new test();return 0;
}

因为用new test()初始化up时,需要将new test()隐式转化为一个unique_ptr<test>对象,需要调用unique_ptr<test>的构造函数,但是unique_ptr<test>的构造函数是explicit,所以转化失败

将第三行代码改为

unique_ptr<test> up(new test());

编译通过

此外,也不要使用new返回的普通指针给一个智能指针赋值,道理同上

同理,如果一个函数的返回值或者形参是一个unique_ptr或shared_ptr,也不要使用new创建的对象作为返回值或者作为参数,道理相同

六、最好不要混合使用智能指针和普通指针

正式因为可以用new返回的内置指针来初始化智能指针,但是如果将new返回的指针绑定到一个智能指针的临时变量上,那么,new的对象就会被无缘无故释放,当再次访问该对象,有可能就会访问到一个空悬指针

示例

void testfunc(shared_ptr<int> sp)
{cout<<sp.use_count()<<endl;
}
int main(int argc, char const *argv[])
{int *p=new int(1024);testfunc(shared_ptr<int>(p));cout<<*p<<endl;return 0;
}

上述代码中p创建了一个int数据,值为1024,用p生成一个shared_ptr<int>的临时对象,然后初始化函数形参sp,接着临时变量自动被释放,sp的引用计数变为1,当函数执行结束时,函数的形参sp作用域结束也被自动释放,引用计数为0,所以int数据所占的内存被释放,主函数中的p成了空悬指针,之后解引用该指针(危!!!),数据失效。

虽然用new初始化shared_ptr会出现上述问题,当时如果用一个shared_ptr的临时对象放入一个容器中,可以使得shared_ptr的引用计数不会增加

示例

void rightdelete()
{vector<shared_ptr<int>> v;for (int i=0;i<3;++i) {v.push_back(shared_ptr<int>(new int(10)));}for (int i=0;i<v.size();++i) {cout<<v[i].use_count()<<endl;}
}

https://blog.csdn.net/Master_Cui/article/details/109264151中提到,当把一个shared_ptr对象放入一个容器中时,shared_ptr对象的引用计数会+1,但是如果当把一个shared_ptr的临时对象放入一个容器中,引用技术并不会增加,因为shared_ptr的临时对象会自动释放。所以当上述函数的执行结束后,v中的元素的引用技术减少为0,指向对象的内存会自动被回收,不用手动delete,防止了内存泄漏

所以,综合第五点和第六点,最好不要使用普通指针来初始化一个智能指针,如果使用shared_ptr,请使用make_shared函数来初始化shared_ptr;如果使用unique_ptr,请使用make_unique函数来进行初始化

如果一个容器中存储了智能指针的对象,当想容器中添加元素时,请使用make_shared函数创建一个临时变量并将该临时变量添加到容器中

七、不要使用get函数给别的智能指针初始化或者赋值

get函数从shared_ptr对象中返回指向对象的普通指针,用于向不能使用智能指针的程序传递一个普通指针,但是get虽然返回一个普通指针,但是不要delete该指针,因为指向的内容的生命周期由智能指针来决定。如果delete该指针,那么智能指针的引用计数为0时,会再次delete,造成二次delete错误。

此外,也不要使用get函数给别的智能指针初始化或者赋值

示例

int main(int argc, char const *argv[])
{shared_ptr<int> p(new int(42));{shared_ptr<int> p2(p.get());cout<<p2.use_count()<<endl;}cout<<p.use_count()<<endl;cout<<*p<<endl; return 0;
}

上述代码中p和p2都是智能指针,且都指向相同的内存,p2通过p.get来初始化p2。但是p和p2的引用计数都是1且p2和p的作用域不同,当p2的作用域结束后,p2的引用计数为0,指向的内存被释放,所以,打印*p时,解引用了空悬指针(危!!!

所以,不要使用get函数给别的智能指针初始化或者赋值,也不要使用相同的普通指针给多个智能指针初始化或者赋值,因为那样引用计数并不会增加,只有使用智能指针初始化或赋值另一个智能指针,引用计数才会增加

另外,当使用get指针时,应当知道当最后一个智能指针销毁后,get返回的指针就是无效的,所以在使用get函数前,先判断一下引用计数是否为0

八、为什么需要weak_ptr

weak_ptr是shared_ptr的附属品,不会增加shared_ptr的引用计数,weak_ptr中的内容有可能是无效的,所以不能直接使用weak_ptr访问指向的对象,而必须调用weak_ptr的成员函数lock,lock返回的是一个shared_ptr,对lock的返回值进行判空后,才能决定是否能访问指向的对象

weak_ptr因为其不增加引用计数的特点,所以经常用来防止shared_ptr出现循环引用导致的内存泄漏

示例

class node
{
public:shared_ptr<node> next;node(int _val):val_(_val) {cout<<__func__<<endl;}~node() {cout<<__func__<<endl;}int val_;
};int main(int argc, char const *argv[])
{shared_ptr<node> pnode=make_shared<node>(10);pnode->next=make_shared<node>(11);return 0;
}

上述代码实现了一个单链表,并且创建了两个节点,当主函数执行结束后,两个智能指针的生命周期结束。调用了两次构造函数,内存被正确释放

现在,添加一个pre成员,指向前一个节点,实现一个双向链表,情况如下

class node
{
public:shared_ptr<node> next;shared_ptr<node> pre;node(int _val):val_(_val) {cout<<__func__<<endl;}~node() {cout<<__func__<<endl;}int val_;
};int main(int argc, char const *argv[])
{shared_ptr<node> pnode=make_shared<node>(10);pnode->next=make_shared<node>(11);pnode->next->pre=pnode;cout<<pnode.use_count()<<endl;cout<<pnode->next.use_count()<<endl;cout<<pnode->next->pre.use_count()<<endl;return 0;
}

通过上述打印结果可知,调用了两次构造函数,但是没有调用析构函数,说明出现了内存泄漏。原因为是因为当执行pnode->next->pre=pnode;后,pnode的引用计数变为2,主函数退出后,pnode的的引用计数-1,变成1,引用计数不为0,所以,没有调用pnode的析构函数,进而没有调用pnode->next的析构函数,所以两个node对象都没有被正确释放,产生内存泄漏

解决方案:

将shared_ptr<node> pre;改为weak_ptr<node> pre;

这是因为weak_ptr本身不会增加绑定的shared_ptr的引用计数,所以当调用pnode->next->pre=pnode;后,pnode的引用计数依然是1,主函数执行结束后,调用pnode的析构函数,而pnode->next的引用计数也为1,所以也调用pnode->next的析构函数,这两个指针的引用计数均为0了,释放各自指向对象的内存,打印出node析构函数的log

所以,weak_ptr很好的解决了shared_ptr循环引用的问题,这就是weak_ptr最大的价值

九、编写自定义的删除器

除了使用default_delete,还可以通过函数指针自定义删除器

void deleter(int *p)
{delete p;
}void arrarydeleter(int *p)
{delete []p;
}void customdeleter()
{shared_ptr<int> sp(new int[10], arrarydeleter);shared_ptr<int> sp1(new int[10], default_delete<int[]>());unique_ptr<int, void (*)(int *)> up(new int(10), deleter);unique_ptr<int[], void (*)(int *)> up1(new int[10]());
}

如果用shared_ptr指向一个数组,那么需要自定义删除器,或者使用default_delete<int[]>(),shared_ptr的自定义的删除器可以不做为模板参数传入,但是unique_ptr的自定义删除器必须作为模板参数传入,必须指明删除器的类型。unique_ptr如果不指定删除器,默认删除器执行delete []T

参考

《C++ 标准库》

《C++ Primer》

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

C++知识点36——使用智能指针的注意事项(下)相关推荐

  1. C++知识点35——使用智能指针的注意事项(上)

    一.shared_ptr, weak_ptr和unique_ptr之间的赋值操作 1.shared_ptr之间的赋值操作 当使用shared_ptr对象进行赋值时,左值会指向新的对象,所以左值的引用计 ...

  2. c++ 智能指针_详解 C++ 11 中的智能指针

    C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念.这里不置贬褒,手动分配内存与手动释放内 ...

  3. C++ 以智能指针管理内存资源

    1.简介 C++ 作为一门应用广泛的高级编程语言,却没有像 Java.C# 等语言拥有垃圾回收(Garbage Collection )机制来自动进行内存管理,这也是C++ 一直被诟病的一点.C++ ...

  4. C++智能指针 intrusive_ptr

    intrusive_ptr: intrusive_ptr是一个侵入式的引用计数型指针,它可以用于以下两种情形: [1]对内存占用的要求非常严格,要求必须与原始指针一样: [2]现存代码已经有了引用计数 ...

  5. 代码注释法学习智能指针intrusive_ptr

    智能指针intrusive_ptr一般情况下不要使用,除非被指类的某个成员函数需要返回this指针. 因为intrusive_ptr需要自己实现引用计数,所以实现起来比较复杂.还要实现intrusiv ...

  6. C++智能指针:更简单、更高效的内存管理方法

    C++智能指针:从新手到高手的心理密码C++ Smart Pointers: Psychological Passcodes from Beginner to Expert 智能指针简介 (Intro ...

  7. cwinthread*线程指针怎么销毁结束_C++知识点:智能指针

    之前面试虾皮时问到了智能指针相关知识点,当时答的很没有条理,这里整理一下权当笔记. 根据<C++ Primer Plus>中的解释,智能指针是行为类似于指针的模板对象.当函数分配堆内存时, ...

  8. C++知识点43——解引用运算符和箭头运算符的重载及智能指针类的实现

    一.概念. 在自定义行为类似指针的类时,需要重载*和->.C++中的智能指针就重载了这两个运算符.->必须是成员函数,*也应该是成员函数.与内置类型保持一致,这两个函数通常都是const的 ...

  9. C++知识点34——动态内存与智能指针

    一.动态内存 动态内存所在的位置在堆区,由程序员手动分配并手动释放,而不像栈内存由系统分配和自动释放 C++通过new运算符为对象在堆上分配内存空间并返回该对象的地址,并用delete运算符销毁对象并 ...

最新文章

  1. 一些有用的webservice
  2. 【图灵奖大佬】Yoshua Bengio最新《深度学习》教程
  3. ios cordova报gap://ready 弹出框,一直弹
  4. python自学笔记之开源小工具:SanicDB介绍
  5. 电子换向电动机行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  6. 自定义事件(如未作说明,本博客文档都是用C#代码)
  7. 控制层@Value注解取不到值
  8. Redis实现分布式锁2
  9. 转帖-win2003各版本的区别
  10. 人脸识别-YOLOv5模型目标检测
  11. java 的 sort()_Java中Array.sort()的几种用法
  12. 2020年第六届 美亚杯电子取证 团体赛 wp
  13. 数据结构与算法教程——App推荐
  14. android rn框架开发的例子,RN与安卓通信架构篇
  15. 盛唐气象:李白的诗与酒
  16. 将矩形图片绘制成圆形图片
  17. 华为认证HCIA-Datacom知识点
  18. 宁夏小学三年级计算机下册教案,【宁夏三年级信息技术下册教案资讯】宁夏三年级信息技术下册教案足球知识与常识 - 足球百科 - 599比分...
  19. linux系统windows模拟器下载,Wine 1.7.40 发布下载,Windows 模拟器
  20. linux命令part,技术|十个鲜为人知的 Linux 命令-Part 3

热门文章

  1. 微信支付的坑 返回值 -1
  2. 【转】java线程系列---Runnable和Thread的区别
  3. html Frame、Iframe、Frameset 的区别 详细出处参考:http://www.jb51.net/web/22785.html
  4. Linux大文件处理,伪分区
  5. 很实用的 “设为首页”与“加入收藏”代码
  6. 父子对等组之间的关系
  7. python之变量操作
  8. FTPVSFTPD安装和参数说明
  9. FPGA黑金开发板mini版新鲜出炉!!!
  10. SSM实现个人博客系统