C++智能指针
std::shared_ptr 共享指针
b站:C++的共享指针 shared_ptr 如何帮你自动管理内存 、 C++ 你可能不知道的 shared_ptr 补充知识点
比如:std::shared_ptr<wenet::FeaturePipelineConfig> feature_config_ = nullptr;
解释
共享指针会记录有多少个共享指针指针指向同一个物体(或者说对象,object),当这个数字将为0时,程序就会自动释放这个物体,省去我们手动delete的烦恼。
因此用共享指针的好处是可以自动管理内存。用于多个指针指向同一个资源的场景。
因为需要引用计数,有额外开销,因此操作性能(或者说速度)相比于裸指针会一点点下降,可能不适合应用于对性能要求严苛的场景。
写法
要使用智能指针首先需要 #include <memory>
,在std命名空间中,所以可以先写上 using namespace std;
(这样后面就不用写std::
,不然还要注明是 std::
)
1 |
|
make_shared
会动态分配一块内存,创建对应的资源,然后让shared_ptr指针指向这块内存。
make_shared
接受一个类型和对应的初始化参数。
创建完成后,就可以像普通指针一样使用shared_ptr。
1 | shared_ptr<int> p2 = p; |
shared_ptr<int> p2 = p;
是复制shared_prt,让多个指针指向同一个物体,这就是共享。
几种创建方法
- 通过如下 2 种方式,可以构造出 shared_ptr
类型的空智能指针:
1 | std::shared_ptr<int> p1; //不传入任何实参 |
注意,空的 shared_ptr 指针,其初始引用计数为 0,而不是 1。
- 在构建 shared_ptr 智能指针,也可以明确其指向。例如:
1 | std::shared_ptr<int> p3(new int(10)); |
由此,我们就成功构建了一个 shared_ptr 智能指针,其指向一块存有 10 这个 int 类型数据的堆内存空间。
同时,C++11 标准中还提供了 std::make_shared
1 | std::shared_ptr<int> p3 = std::make_shared<int>(10); |
以上 2 种方式创建的 p3 是完全相同。
- 除此之外,shared_ptr
模板还提供有相应的拷贝构造函数和移动构造函数,例如:
1 | //调用拷贝构造函数 |
如上所示,p3 和 p4 都是 shared_ptr 类型的智能指针,因此可以用 p3 来初始化 p4,由于 p3 是左值,因此会调用拷贝构造函数。需要注意的是,如果 p3 为空智能指针,则 p4 也为空智能指针,其引用计数初始值为 0;反之,则表明 p4 和 p3 指向同一块堆内存,同时该堆空间的引用计数会加 1。
而对于 std::move(p4) 来说,该函数会强制将 p4 转换成对应的右值,因此初始化 p5 调用的是移动构造函数。另外和调用拷贝构造函数不同,用 std::move(p4) 初始化 p5,会使得 p5 拥有了 p4 的堆内存,而 p4 则变成了空智能指针。
注意,同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常。例如:
1 | int* ptr = new int; |
- 在初始化 shared_ptr 智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用我们自定义的释放规则。
在某些场景中,自定义释放规则是很有必要的。比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。
见下面的 “自定义删除函数”。
引用计数
有多少个shared_ptr指向某个物体。
举例:
1 | class Ball{ |
创建3个共享指针指向同一个ball类。依次调用use_count()查看有多少指针指向这个物体。
p.reset()
,shared_ptr调用reset()后会重置,不再指向原来的物体,这里函数内即使不加reset(),函数结束后也会自动释放,也就是调用~Ball()
。
3个shared_ptr都释放后,没有shared_ptr指向开头的ball,就自动释放。?
- todo:p.reset();之后再访问p->Bounce();为什么还能打印??
reset()传参数,指向新物体
1 | shared_ptr<Ball> sp; |
sp = reset(new Ball)
表示sp指向一个新ball类对象物体,旧的ball引用计数-1。
自定义删除函数 让shared_ptr释放资源 default_delete
当名叫sfp的shared_ptr引用计数降为0时,执行close_file这个函数
1 | void close_file(FILe* fp){ |
1 | //指定 default_delete 作为释放规则 |
shared_prt得到裸指针
p.get()
,比如 Ball* rp = p.get()
, 其中p是shared_prt,假设Ball函数接收的参数只能是裸指针(不能接收智能指针),则可以通过调用shared_ptr的 get()
方法,来获取一个裸指针,再指向当前的内存资源。
一块内存资源,同时有智能指针和裸指针指向它时,当所有的智能指针都被摧毁,但是裸指针仍然存在时,此时资源仍然会被释放,此时再用裸指针访问那块资源,会变成未定义的行为,因此此时也就不用释放裸指针了。因此用共享指针时避免和裸指针混用。
aliasing 别名
举例:
1 | struct Bar { int i = 123; }; |
std::unique_ptr 独享指针
解释
是一种零开销的智能指针,可以实现自动内存管理,也没有额外的性能开销。资源独享,不能复制。
写法
1 |
|
和裸指针类似的用法:
1 | auto ball = make_unique<Ball>(); |
想获得资源的裸指针:
1 | Ball* p = up->get(); |
释放资源:
1 | up.reset(); |
up.reset()
会释放unique_ptr下面的资源,并把该unique_ptr设置为nullptr
资源解绑:
1 | Ball* ball = up.release(); |
up.release()
返回资源的裸指针,同时把该unique_ptr设置成nullptr。
自定义分配函数和释放函数 decltype 。
函数间传递unique_ptr
函数传参很多时候是传值,需要复制。而unique_ptr不支持复制,因此容易写错。
举例,以下代码会编译错误:
1 | void pass_up(unique_str p){ |
调用pass_up(up)
时,会产生一个复制up
的操作,这是不允许的。
修改如下:
- 访问unique_str的资源
如果不需要传递指针本身,要的仅仅是内容的话:
1 | void pass_up(int& value){ |
或者 传递裸指针(裸指针作为参数传递):
1 | void pass_up(int* p){ |
- 通过函数改变unique_str本身
函数的参数设计为unique_ptr的引用
1 | void pass_up(unique_prt<int>& up){ |
或者 用 std::move
1 | void pass_up(unique_prt<int> up){ |
move
可以转移unique_ptr对资源的控制权。