C++智能指针shared_ptr、unique_ptr以及weak_ptr
目录
shared_ptr类
shared_ptr和unique_ptr都支持的操作
shared_ptr独有的操作
make_shared函数
shared_ptr自动销毁所管理的对象
由普通指针管理的动态内存在被显式释放前一直会存在
shared_ptr和new结合使用
定义和改变shared_ptr的其他方法
不要混合使用普通指针和智能指针
多个独立的shared_ptr指向同一块内存,会发生内存泄漏问题:
值传递时,实参会被拷贝,会递增其引用计数,局部变量被释放后,计数减一
不要使用get初始化另一个智能指针或为智能指针赋值
其他shared_ptr操作
unique_ptr类
unique_ptr操作:release和reset
unique_ptr作为函数参数和返回值
weak_ptr类
常用函数
shared_ptr循环引用问题
使用智能指针的目的:为了更容易更安全的使用动态内存
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。
新标准库提供的两个智能指针shared_ptr和unique_ptr的区别在于管理底层指针的方式:
- shared_ptr允许多个指针指向同一个对象
- unique_ptr则“独占”所指向的对象
- weak_ptr则是一种弱引用,指向shared_ptr所管理的对象
shared_ptr类
智能指针也是模板,所以在创建一个智能指针时,必须提供指针指向的类型:
shared_ptr<string> p1;shared_ptr<list<int> > p2;
- 默认初始化的智能指针中保存着一个空指针。
- 使用方式与普通指针类似,解引用一个智能指针返回它指向的对象。
- 如果在一个条件判断中使用智能指针,效果就是检测它是否为空
shared_ptr和unique_ptr都支持的操作
- shared_ptr<T> sp 、unique_ptr<int> up 空智能指针,可以指向类型为T的对象
- p 将p用作一个条件判断,若p指向一个对象,则为true
- *p 解引用p,获得它指向的对象
- p->mem 等价于(*p).men
- p.get() 返回p中保存的指针,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
- swap(p,q) 交换p和q中指针 ,p.swap(q)
shared_ptr独有的操作
- make_shared<T> (args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。
- shared_ptr<T>p(q) p是shared_ptr q的拷贝:此操作会递增q中的计数器。q中的指针必须能转换为T*
- p=q p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数。若p的引用计数变为0,则将其管理的原内存释放
- p.unique() 若p.use_count()为1,返回true,否则返回false
- p.use_count() 返回与p共享对象的智能指针数量:可能很慢,主要用于调试
make_shared函数
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数
//指向一个值为666的int的shared_ptrshared_ptr<int> p1 = make_shared<int>(666);//指向一个值为“999999999”的stringshared_ptr<string> p2 = make_shared<string>(10,'9');//指向一个值初始化的int,即值为0;shared_ptr<int> p3 = make_shared<int>();cout << *p1 << endl;//666cout << *p2 << endl;//9999999999cout << *p3 << endl;//0
类似顺序容器的emplace成员,make_shared用其参数来构造给定类型的对象。例如调用make_shared<string>时传递的参数必须与string的某个构造函数相匹配。如果不传递任何参数,对象就会进行值初始化。
shared_ptr自动销毁所管理的对象
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。其析构函数会递减它所指向对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
void use1(T arg){shared_ptr<Foo> p = factory(arg);
//使用p
}//p离开了作用域,它指向的内存会被自动释放掉
由于p是use1的局部变量,在use1结束时它将被销毁,当p被销毁时,将递减其引用计数并检查它是否为0。在此例中,p是唯一引用factory返回的内存的对象。
但是有其他shared_ptr也指向这块内存,她就不会被释放掉:
void use1(T arg){shared_ptr<Foo> p = factory(arg);
//使用preturn p;//当我们返回p时,引用计数进行了递增操作
}//p离开了作用域,它指向的内存不会被自动释放掉
由普通指针管理的动态内存在被显式释放前一直会存在
void use2(T arg){Foo* p = factory(arg);
//使用p,但不delete它}//p离开了作用域,它指向的内存不会被自动释放掉
shared_ptr和new结合使用
接受指针参数的智能指针构造函数是explicit的,因此我们不能将一个内置指针隐式转换为一个智能指针,必须用直接初始化的方式来初始化一个智能指针:
shared_ptr<int> p1 = new int(1024); //错误
shared_ptr<int> p2(new int(1024)); //正确shared_ptr<int> clone(int p){return new int(p); //错误
}
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存。
定义和改变shared_ptr的其他方法
- shared_ptr<T> p(q) p管理内置指针q所指向的对象;q必须是指向new分配的内存,且能够转换为T*类型
- shared_ptr<T> p(u) p从unique_ptr u那里接管了对象的所有权:将u置为空
- shared_ptr<T> p(q,d) p接管了内置指针q所指向的对象所有权,q必须能够转换为T*类型。p将使用可调用对象d来代替delete
- p.reset() ,p.reset(q) 若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空
不要混合使用普通指针和智能指针
shared_ptr可以协助对象的析构,但这仅限于自身的拷贝之间。
推荐使用make_shared而不是new的原因:
再分配对象的同时就将shared_ptr与之绑定,从而避免无意中将同一块内存绑定到多个独立创建的shared_ptr上
多个独立的shared_ptr指向同一块内存,会发生内存泄漏问题:
int * a = new int(999);shared_ptr<int> p1(a);shared_ptr<int> p2(a);cout << *p1 << endl;//999cout << p1.use_count() << endl;//1cout << *p2 << endl;//999cout << p2.use_count() << endl;//1//会发生内存泄漏,因为两个独立的shared_ptr同时指向了一个内存空间,该内存会被释放两次
正确做法:使用另一个智能指针初始化其他智能指针
int * a = new int(999);shared_ptr<int> p1(a);shared_ptr<int> p2(p1);//使用p1初始化p2,两个指针的计数器都为2,安全cout << *p1 << endl;//999cout << p1.use_count() << endl;//2cout << *p2 << endl;//999cout << p2.use_count() << endl;//2
值传递时,实参会被拷贝,会递增其引用计数,局部变量被释放后,计数减一
void test(shared_ptr<int> p) {++*p;cout <<"值传递时,引用计数加一:"<< p.use_count() << endl;//2
}
int main(void) {int * a = new int(999);shared_ptr<int> p1(a);cout << *p1 << endl;//999cout << p1.use_count() << endl;//2test(p1);cout << *p1 << endl;//1000cout << p1.use_count() << endl;//1return 0;
}
当一个shared_ptr绑定到一个普通指针时,我们就将内存的管理交给了这个shared_ptr,就不应该再使用普通指针来访问shared_ptr所指向的内存。
void test(shared_ptr<int> p) {++*p;cout <<"引用计数为1:"<< p.use_count() << endl;//1
}
int main(void) {int *x(new int(1024));//x 是一个普通指针,不是智能指针//test(x); 错误,不能将int*转换为一个shared_ptr<int>test(shared_ptr<int>(x));//合法,但离开函数体后内存会被释放,里面的引用计数为1cout << *x << endl;//此时x是个悬空指针,其值未定义return 0;
}
不要使用get初始化另一个智能指针或为智能指针赋值
get()函数返回一个内置指针,指向智能指针管理的对象。使用场景:我们需要向不能使用智能指针的代码传递一个内置指针。使用get返回的指针的代码不能delete指针。
其他shared_ptr操作
使用reset来将一个新的指针赋予一个shared_ptr:
p = new int(1024); //错误:不能将一个指针直接赋值给shared_ptr
p.reset(new int(1024)); //正确:p指向一个新对象
与赋值类似,reset会更新引用计数,如果需要的话,会释放p指向的对象。
智能指针使用总结:
- 不能使用相同的内置指针初始化(或reset)多个智能指针(会出现多个独立且指向同一内存的指针)
- 不delete get()返回的指针
- 不使用get()初始化或reset另一个智能指针
- 如果使用get()返回的指针,在最后一个对应的智能指针销毁后,该指针无效
- 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
unique_ptr类
一个unique_ptr”拥有“它所指向的对象。与shared_ptr不同,某时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
与shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化的形式:
unique_ptr<string> p1(new string("hello MTK"));//unique_ptr<string> p2(p1);//错误:unique_ptr不支持拷贝unique_ptr<string> p3;//p3 = p1;//错误:unique_ptr不支持赋值
unique_ptr操作:release和reset
- unique_ptr<T> u1 空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针;
- unique_ptr<T,D> u2 使用一个类型为D的可调用对象来释放它的指针
- unique_ptr<T,D> u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
- u=nullptr 释放u指向的对象,将u置为空
- u.release() u放弃对指针的控制权,返回指针,并将u置为空
- u.reset() 释放u指向的对象
- u.reset(q) 如果提供了内置指针q,令u指向这个对象;否则将u置为空
- u.reset(nullptr)
虽然我们不能拷贝或赋值unique_ptr,但可以通过release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr:
unique_ptr<string> p1(new string("hello MTK"));cout << *p1 << endl;//将所有权从p1转移给p2unique_ptr<string> p2(p1.release());//release将p1置为空unique_ptr<string> p3(new string("hello world"));//将所有权从p3转移给p2p2.reset(p3.release());//reset释放了p2原来指向的内存
release成员返回unique_ptr当前保存的指针并将其置为空。因此,p2被初始化为p1原来保存的指针,而p1被置为空。
reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原来指向的对象被释放。因此,对p2调用reset释放了用“hello MTK”初始化的string所使用的内存,将p3对指针的所有权转移给p2,并将p3置为空。
调用release会切断unique_ptr和它原来管理的对象间的联系。release返回的指针通常用来初始化另一个智能指针或给另一个智能指针赋值。如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:
p2.release(); //错误,p2不会释放内存,而且我们丢失了指针
auto p = p2.release(); //正确,当时我们必须记得delete(p)
unique_ptr作为函数参数和返回值
不能拷贝unique_ptr的规则有个例外:我可以拷贝或赋值一个将要被销毁的unique_ptr,比如作为函数参数或返回值:
unique_ptr<int> clone1(int p) {//正确:从int*创建一个unique_ptr<int>return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone2(int p) {//正确:从int*创建一个unique_ptr<int>unique_ptr<int> ret(new int(p));return ret;
}
weak_ptr类
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。它是一种弱引用。
常用函数
- weak_ptr<T> w 空weak_ptr可以指向类型为T的对象
- weak_ptr<T> w(sp) 与shared_ptr sp 指向相同对象的weak_ptr。T必须能转换为sp指向的类型
- w = p ,p可以是一个shared_ptr或者weak_ptr,赋值后w与p共享对象
- w.reset() 将w置为空
- w.use_count() 与w共享对象的的shared_ptr的数量
- w.expired() 若w.use_count()为0,返回true,否则返回false
- w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr
当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:
auto p = make_shared<int>(42);weak_ptr<int> wp(p); //wp弱共享p;p的引用计数不会改变
本例中,wp和p指向相同的对象,由于是弱共享,创建wp不会改变p的引用计数;wp指向的对象可能被释放掉。
由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否存在。如果存在,lock返回一个指向共享对象的shared_ptr。与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。
shared_ptr<int> p1(new int(666));weak_ptr<int> wp(p1);cout << wp.use_count() << endl;//1if (shared_ptr<int> sp = wp.lock()) {(*sp)++;cout << sp.use_count() << endl;//2cout << p1.use_count() << endl;//2}
shared_ptr循环引用问题
class B;
class A
{
public:shared_ptr<B> pb_;~A(){cout << "析构A" << endl;}
};
class B
{
public:shared_ptr<A> pa_;~B(){cout << "析构B" << endl;}
};
void testShared_ptr() {shared_ptr<B> pb(new B);shared_ptr<A> pa(new A);pb->pa_ = pa;pa->pb_ = pb;cout << "pb 的引用计数:"<< pb.use_count() << endl;cout << "pa 的引用计数:"<< pa.use_count() << endl;
}
int main(void) {//pb 的引用计数:2//pa 的引用计数:2// 且未调用析构函数,说明两块内存一直没被释放testShared_ptr();//跳出函数后,二者的引用计数减一,但还是1,不为0,所以内存得不到释放
}
如上程序,会造成shared_ptr间的循环引用问题,A类中有shared_ptr<B> 指针,B类中有shared_ptr<A>指针。现创建A类和B类对象,并使用shared_ptr,pb,pa指向它们。同时使A对象和B对象的成员变量--各自的shared_ptr指针pa_,pb_指向对方。这就造成了循环引用:
有两个内存块,但是有四个shared_ptr指针,两个shared_ptr指向同一块内存空间:
- pa和pa_都指向A对象的内存空间
- pb和pb_都指向B对象的内存空间
使得其该内存块的引用计数为2。当跳出函数时,局部变量pb,pa被销毁,所以引用计数减一,但引用计数仍为1,不为0,所以A对象和B对象的内存得不到释放(其析构函数没有被调用)。
解决办法:使用weak_ptr
class B;
class A
{
public:weak_ptr<B> pb_;~A(){cout << "析构A" << endl;}
};
class B
{
public:shared_ptr<A> pa_;~B(){cout << "析构B" << endl;}
};
void testShared_ptr() {shared_ptr<B> pb(new B);shared_ptr<A> pa(new A);pb->pa_ = pa;pa->pb_ = pb;//shared_ptr可以直接给weak_ptr赋值cout << "pb 的引用计数:"<< pb.use_count() << endl;cout << "pa 的引用计数:"<< pa.use_count() << endl;
}
int main(void) {// pb 的引用计数:1// pa 的引用计数:2// 析构B// 析构AtestShared_ptr();
}
运行过程解析:
- 使用weak_ptr指向shared_ptr指向的内存,不会使shared_ptr内的引用计数加一(弱引用),所以指向B对象内存的引用计数为1,指向A对象的引用计数仍为2.
- 跳出函数后,局部变量pb和pa被析构,其指向的内存引用计数都要减一,所以指向B对象的引用计数为0,指向A对象的引用计数为1。此时将析构B对象,所以先调用了B的析构函数。
- B对象被析构,其内部的pa_成员也将被析构,导致指向A对象的引用计数减一,为0。
- A对象的引用计数为0,析构A对象。所以最后调用A的析构函数。
参考书籍:
《C++ Primer 第五版》
C++智能指针shared_ptr、unique_ptr以及weak_ptr相关推荐
- C++ 使用智能指针shared_ptr/unique_ptr管理数组
目录 零.要管理的类 一.使用shared_ptr管理数组 二.使用unique_ptr管理数组 1.第一种方式 2.第二种方式 关于shared_ptr/unique_ptr的基础,我不在本篇博客中 ...
- 32. 对c++中的smart pointer四个智能指针shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解
C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用. 智能指针的作用是管理一个指针, ...
- C++11智能指针(unique_ptr、shared_ptr、weak_ptr)boost::scoped_ptr
C++11智能指针(unique_ptr.shared_ptr.weak_ptr)_-码农小非-的专栏-CSDN博客_c++ shared_ptr weak_ptr 原创)智能指针拾遗 (原创)智能指 ...
- 智能指针(shared_ptr、unique_ptr、weak_ptr)的使用
智能指针的使用 一.shared_ptr 1.创建一个shared_ptr 2.shared_ptr的常用成员函数 reset成员函数的使用 3.==注意事项== 二.unique_ptr 1.uni ...
- 智能指针shared_ptr的用法
智能指针shared_ptr的用法 2016-12-03 15:39 by jiayayao, 360 阅读, 0 评论, 收藏, 编辑 为了解决C++内存泄漏的问题,C++11引入了智能指针(Sma ...
- 智能指针shared_ptr
如果有可能就使用unique_ptr,然后很多时候对象是需要共享的,因此shared_ptr也就会用得很多.shared_ptr允许多个指向同一个对象,当指向对象的最后一个shared_ptr销毁时, ...
- 智能指针shared_ptr的几个例子
#include <string> #include <iostream> #include <memory> //智能指针定义在头文件memory中,例如shar ...
- 智能指针shared_ptr的原理、用法和注意事项
智能指针shared_ptr的原理.用法和注意事项 1 前言 2 shared_ptr原理 3 shared_ptr的基本用法 3.1 初始化 3.2 获取原始指针 4 智能指针和动态数组 4.1 c ...
- 深入学习c++--智能指针(三) unique_ptr
1. 几种智能指针 1. auto_ptr: c++11中推荐不使用他(放弃) 2. shared_ptr: 拥有共享对象所有权语义的智能指针 3. unique_ptr: 拥有独有对象所有权语义的智 ...
最新文章
- 2022-2028年中国TAC薄膜行业市场全景评估及投资前景规划报告
- NLP千亿预训练模型的“第四范式”之Prompt Learning介绍分享
- 用什么样的个人笔记类软件?OneNote、EverNote(印象笔记)、为知笔记、麦库记事、有道云笔记……...
- 吴恩达深度学习课程deeplearning.ai课程作业:Class 4 Week 2 Keras - Tutorial - Happy House
- python吃内存还是cpu_Python2 得到 CPU 和内存信息要怎么实现呢?
- Sentinel-Dashboard 与 apollo 规则的相互同步
- Android广播的学习和使用
- C++ httpclient
- 网络原理题+复习资料
- easyui 添加下拉框数据_功能更新:熟用仪表盘这个功能,你可以少建90%的数据报表...
- oracle中的sql文本类型,Oracle数据库的空间数据类型
- TLS 1.3套件TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256
- rpc调用 java_RPC调用的简单实现
- 微信小程序的开发需要什么费用
- 关于XD卡写保护问题!
- 哪个城市美女最多?OPPO R11开启“谁是拍照King·仲夏之梦”活动
- 前端和后端的英文_前端、后端、全栈是什么意思?薪资怎么样?
- 强制在线带修区间LCM(线段树+质因子状压)
- Java基础语法96-Java91011新特性
- 阿里云图标使用 (symbol 引用方式)
热门文章
- 计算机程序由算法,计算机程序算法试题.pdf
- Windows远程桌面连接不上提示出现了内部错误的解决办法
- 超级计算机排名2019世界500强,2019年全球超级计算机500强名单: TOP500排名速度如何变化?...
- Android 解压zip压缩包 (压缩包内有多级目录)
- No.14 交易平台初探【交易平台系列①】
- 【黑金动力社区】【原创博文集锦】《液晶驱动与GUI 基础教程》导读
- 二维码工具-QrCodeUtil
- error: AC_CONFIG_MACRO_DIRS([m4]) conflicts with ACLOCAL_AMFLAGS=-I m4 解决方法
- 单片机电子时钟源代码
- 一文读懂多分类的评价指标(微平均、宏平均、加权平均)