C++中的四个智能指针
只能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。智能指针定义在memory头文件中。
1. auto_ptr(C++11已经舍弃)
由new expression获得的对象,在auto_ptr对象销毁时,他所管理的对象也会自动被delete掉。
auto_ptr<string> p1(new string("This is a string"));
auto_ptr<string> p2;
p2 = p1;
上述语句中,如果p1和p2是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次。
要避免这种问题,方法有多种:
- 定义赋值运算符,使之执行深拷贝。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
- 建立所有权概念。对于特定的对象,只能有一个指针可拥有,这样所拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略。
- 跟踪引用对象的智能指针数。这称为引用计数。例如:赋值时,计数加1,指针过期时,计数减1。当减为0时才调用delete。这就是sheared_ptr采用的策略。
为什么要弃用auto_ptr?
#include<memory>
#include<iostream>
using namespace std;
int main()
{
auto_ptr<string> p1(new string("This is a string"));
auto_ptr<string> p2 = p1; //p1将所有权转让给p2,此时p1不再引用该字符串从而变成空指针。
cout << *p1 << endl; //报错,此时p1已经没有所指向的内存的所有权。
cout << *p2 << endl;
}
- 使用shared_ptr时运行正常,因为shared_ptr采用引用计数,p1和p2都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。
- 使用unique_ptr时编译出错,与auto_ptr不同的是,使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译时出现错误。
舍弃auto_ptr的原因:避免因潜在的内存问题导致程序崩溃
2. unique_ptr(替换auto_ptr)
unique_ptr比auto_ptr更加安全,因为auto_ptr有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr禁止了拷贝语义,但提供了移动语义,即可以使用move()进行控制权限的转移。
unique_ptr<string> p1(new string("This is a string"));
unique_ptr<string> p2(p1); //编译出错,已禁止拷贝
unique_ptr<string> p3 = p1; //编译出错,已禁止拷贝
unique_ptr<string> p4 = std :: move(p1); //控制权限转移
auto_ptr<string> p1(new string("This is a string"));
auto_ptr<string> p2(p1); //编译通过,运行出错
auto_ptr<string> p3 = p1; //编译通过,运行出错
如果unique_ptr是个临时右值,编译器允许拷贝语义。
unique_ptr<string> demo(string *s)
{
unique_ptr<string> temp(new string(s));
return temp;
}
unique_ptr<string> ps;
ps = demo("This is a string");
demo()返回一个临时unique_ptr,然后ps接管了临时对象所管理的资源,而返回时临时的unique_ptr被销毁,也就是说没有机会使用unique_ptr来访问无效的数据。相对于auto_ptr任何情况下都允许拷贝语义,这正是unique_ptr更加灵活的地方。
扩展auto_ptr不能完成的功能:
- unique_ptr可放在容器中,弥补了auto_ptr不能作为容器元素的缺点。
- 管理动态数组
- 自定义资源删除操作。
3. shared_ptr(引用计数型智能指针)
类似vector,智能指针也是模板,当我们创建一个智能指针时,必须提供额外的信息---指针可以指向的类型。
shared_ptr<string> p1;
默认初始化的智能指针中保存着一个空指针。
智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。
make_shared函数
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<string> p2 = make_shared<string>(10, '9');
auto p3 = make_shared<vector<string>>();
shared_ptr的拷贝和赋值
- 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。
- 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
shared_ptr自动销毁所管理的对象
- 它是通过另一个特殊的成员函数---析构函数(destructor)完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制该类型的对象销毁时做什么操作。
- 析构函数一般用来释放对象所分配的资源。
- shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
shared_ptr还会释放相关联的内存
- shared_ptr在无用之后仍然保留的一种可能情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保使用erase删除哪些不再需要的shared_ptr元素。
使用动态生存期的资源的类
- 程序不知道自己需要使用多少个对象(容器类)
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据(如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面的销毁底层数据。)
4. weak_ptr(辅助shared_ptr)
- weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
- 创建一个weak_ptr时,要用一个shared_ptr来初始化它
- 不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr.
5.shared_ptr循环引用
#include<iostream>
#include<memory>
using namespace std;
class ListNode{
public:
int m_value;
shared_ptr<ListNode> prev;
shared_ptr<ListNode> next;
//构造函数
ListNode(int value):m_value(value){
cout << "constructor called!" <<endl;
}
//析构函数
~ListNode(){
cout << "destructor called!" <<endl;
}
};
void test(){
shared_ptr<ListNode> sp1 = make_shared<ListNode>(33);
shared_ptr<ListNode> sp2 = make_shared<ListNode>(44);
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1 -> next = sp2;
sp2 -> prev = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main(){
test();
return 0;
}
//运行结果:
constructor called!
constructor called!
1
1
2
2
构造的sp1和sp2在出它们的作用域(即test())时,都没有被析构,从而造成了内存泄漏。
6.weak_ptr是如何解决循环引用的?
#include<iostream>
#include<memory>
using namespace std;
class ListNode{
public:
int m_value;
weak_ptr<ListNode> prev;
weak_ptr<ListNode> next;
//构造函数
ListNode(int value):m_value(value){
cout << "constructor called!" <<endl;
}
//析构函数
~ListNode(){
cout << "destructor called!" <<endl;
}
};
void test(){
shared_ptr<ListNode> sp1 = make_shared<ListNode>(33);
shared_ptr<ListNode> sp2 = make_shared<ListNode>(44);
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1 -> next = sp2;
sp2 -> prev = sp1;
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main(){
test();
return 0;
}
//运行结果:
constructor called!
constructor called!
1
1
1
1
destructor called!
destructor called!
弱引用不修改对象的引用计数;弱引用能检测对象是否已经被释放。
只要把循环引用的一方使用弱引用,即可解除循环引用。
C++中的四个智能指针的更多相关文章
- c++中的四种智能指针
c++中的四种智能指针 写惯了python,golang再来写c++总觉得头大,很大一个原因就是他没有一个GC机制. 不过c++中提供了智能指针,也不是不能用,李姐万岁! auto_ptr, shar ...
- 转载:STL四种智能指针
转载至:https://blog.csdn.net/K346K346/article/details/81478223 STL一共给我们提供了四种智能指针: auto_ptr.unique_ptr.s ...
- stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结
stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结 1. auto_ptrauto_ptr主要是用来解决资源自动释放的问题,比如如下代码:voi ...
- c++ 中的8种智能指针[转]
一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 ...
- C++面试题(四)——智能指针的原理和实现
C++面试题(一).(二)和(三)都搞定的话,恭喜你来到这里,这基本就是c++面试题的最后一波了. 1,你知道智能指针吗?智能指针的原理. 2,常用的智能指针. 3,智能指针的 ...
- 聊聊 C++ 中的几种智能指针 (下)
一:背景 上一篇我们聊到了C++ 的 auto_ptr ,有朋友说已经在 C++ 17 中被弃用了,感谢朋友提醒,今天我们来聊一下 C++ 11 中引入的几个智能指针. unique_ptr shar ...
- 聊聊 C++ 中的几种智能指针 (上)
一:背景 我们知道 C++ 是手工管理内存的分配和释放,对应的操作符就是 new/delete 和 new[] / delete[], 这给了程序员极大的自由度也给了我们极高的门槛,弄不好就得内存泄露 ...
- STL 智能指针
转自: https://blog.csdn.net/k346k346/article/details/81478223 STL一共给我们提供了四种智能指针:auto_ptr.unique_ptr.sh ...
- Effective Modern C++:04智能指针
裸指针有着诸多缺点:裸指针的声明中看不出它指向的是单个对象还是数组:裸指针的声明中也无法看出使用完它指向的对象后是否需要删除,也就是声明中看不出裸指针是否拥有其指向的对象:即使知道要析构裸指针指向的对 ...
随机推荐
- 备战省赛组队训练赛第十七场(UPC)
upc:传送门 A: 题解[1] G: 题解[1] D,G,H,J,L 题解 by 鲁东大学
- 2018-2-13-win10-UWP-Markdown-含源代码
title author date CreateTime categories win10 UWP Markdown 含源代码 lindexi 2018-2-13 17:23:3 +0800 2018 ...
- Python_全局变量的定义
1.在my套件下新建一个关键字systemkey并进行脚本的编写:创建一个${var1}变量,并赋值为aaaaaaaaaa Set Global Variable ${var1} ...
- 第二阶段:4.商业需求文档MRD:4.PRD-用例和规则
类似之前的泳道图 可以在下面添加一些描述 有时候用图还是会有一些限制 不能够很好的表达
- Mongdb的基本操作及java中用法
Mongdb中所有数据以Bson(类似JSON)的格式存在,可以存储集合,map,二进制文件等多种数据类型. 数据库的常用操作 use [数据库名称];//有就选中,没有就添加并选中show dbs; ...
- Linux Centos7 环境基于Docker部署Zookeeper服务搭建实战
配置Zookeeper安装目录 在宿主机配置zookeeper安装目录:/docker/develop/zookeeper 并且在文件夹创建 data 和logs 目录: mkdir -p /dock ...
- 最详细的自定义Spring Boot Starter开发教程
1. 前言 随着Spring的日渐臃肿,为了简化配置.开箱即用.快速集成,Spring Boot 横空出世. 目前已经成为 Java 目前最火热的框架了.平常我们用Spring Boot开发web应用 ...
- 【题解】P1373 小a和uim之大逃离
[题解]P1373 小a和uim之大逃离 考虑到可能会MLE,考虑状态压缩一下 由于只要得到他们的差就行了,所以直接少记录一维就好了 \(dp(i,j,r,1/0)\)表示在\(i,j\)点,当前ui ...
- 洛谷P1002 过河卒 题解 动态规划
题目链接:https://www.luogu.com.cn/problem/P1002 题目大意 棋盘上\(A\)点有一个过河卒,需要走到目标\(B\)点.卒行走的规则:可以向下.或者向右.同时在棋盘 ...
- 「Luogu P3395」路障 解题报告
点开有惊喜 其实是题面 这D1T1给的很有面子! 我居然做的来! 从左上角走到右上角 然后n<=1000 所以果断放弃DFS,选择BFS 思路还是一样的BFS 证明: 走到一个点的时间越早越好( ...