【C/C++】内存管理(一):shared_ptr

  智能指针是<memory.h>的一部分,这个头文件主要负责C++的动态内存管理。C++的动态内存管理是通过 new/delete 实现,这其实在使用的时候很麻烦。所谓智能指针其实是一些模板类,它们负责自动管理一个指针的内存,免去了手动 new/delete 的麻烦
  侯捷在他的教程中提到:C++中一个 class type 的对象可能有两种特殊的情况:像一个指针(pointer-like class,迭代器、智能指针),或者像一个类(仿函数)。为什么要做一个“像指针”的类?因为可能语言的设计者觉得,承接自C语言的普通指针,其功能已经无法满足C++在C语言之外扩展的新功能的需求了。因此现在需要一种新的指针,它首先是个指针,却能比指针做更多。
  其实智能指针就是指针之外的一层封装,这些智能指针类都重载了 * 和 -> 运算符,因此完全可以当成普通指针去用(这跟迭代器其实有一些相似,都是C++中的一些特殊的指针)。一个 pointer-like class 最基本的特点也就很清晰了:

  1. 有一个数据成员是真正的指针;
  2. 重载了 * 和 -> 运算符;
  3. 它的构造函数需要接收一根真正的指针去为数据成员赋初值。

本文作为智能指针系列的第一部分,主要记录 shared_ptr 相关用法。


shared_ptr

  通常来说,动态申请了一片内存之后,可能会在多个地方会用到。对于裸指针,你需要自己记住在什么地方释放内存,不能在有别的地方还在使用的时候,你就释放,也不能忘记释放。而shared_ptr 对象里,不但有一个真正的指针,还有一个用于维护计数的 count 。有人用到这块内存的时候,count 增加 1,而不用的时候(离开作用域或者生命周期外),count 减少 1,如果一块内存的引用计数为 0,则自动释放内存。

  所谓 share ,就是指这个智能指针指向的内存同时可以被多个 shared_ptr 所指(当然就会产生类似多线程的问题,姑且按住不表)。count 就负责统计当前时刻指向这块内存的 shared_ptr 的个数。很容易想到,这个 count 一定是所有 shared_ptr 共同维护的一个值,因此是 static 的,然而这是极其错误的理解!

  如果这个 count 仅仅是一个 static int ,那么一个 share_ptr 的实例化类,比如 share_ptr<int> 就只有这一个 count,所有 share_ptr<int> 类对象共同维护一个 count,而不管这些对象分别指向哪块内存。这显然是不合理的。

  事实上,通过观察数据结构可知,当指向一块内存的第一个智能指针创建的时候,也会为这块内存在堆上创建一个控制块。即引用计数这个东西是在堆上的,多个智能指针指向堆上的同一块地址,来维护引用计数。

  默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,即 new 出来的内存(在堆上而非栈里)。因为智能指针默认的删除器是 delete。如果不是 new 出来的(比如malloc),需要显式传递删除器。make_shared 会使用其参数来构造一个相应类型的对象,这个也是动态的。


(一)基本操作

1.1 初始化

shared_ptr<string> p1;						//初始化没有赋初值,则p1里面真正的指针被初始化为nullptr
shared_ptr<string> pint1 = make_shared<string>("safe uage!");	//安全的初始化方式

【重点】能用make_shared 就不用其它的。


1.2 改变计数的操作:赋值、拷贝、reset

① 赋值

// #case 1
auto sp1 = make_shared<string>("obj1");			//sp1.use_count() = 1,本质是obj1的引用计数为1
auto sp2 = make_shared<string>("obj2");			//sp2.use_count() = 1,本质是obj2的引用计数为1
auto sp1 = sp2;				//sp1指向obj2,obj2的引用计数为2,sp1和sp2的count都是2,obj1引用计数为0被释放

// #case 2
shared_ptr<int> sp3 (new int, [](int* p){
   delete p;}, std::allocator<int>());
shared_ptr<int> sp4 (sp3);						//sp3 和 sp4 的 count 都是2
shared_ptr<int> sp5 (std::move(sp4));			//sp5 偷走了 sp4 指向那块内存的指针,sp4 的 count 变为 0,其余两个为 2

注:1. 获取一根智能指针 count 值的函数:use_count()

  2. 一根智能指针语义上是指针,语法上是对象,因此访问成员用 " . " 而非 " -> "

  3. std::move 会将 sp4 强制转换为相应的右值,调用 move-ctor ,sp4 失效

  4. sp1 = sp2 修改智能指针的指向,并不是一个原子操作

② 拷贝

auto sp1 = make_shared<string>("obj");						
auto sp2(sp1);									//sp1和sp2指向同一个对象,二者的count都是2
func(sp2);										//※

  对于func(sp2),需要分情况讨论:

  • 当 func 的参数是一个 shared_ptr<string> 对象时,由于传参过程中发生值拷贝,则 func 执行过程中,有 sp1 和 sp2 以及 pass by value 生成的 sp2’ 三根智能指针 指向 obj ,因此三者的 count 都是3。func 结束后,由于 sp2’ 是 auto 生命期的变量,会被自动释放,因此 sp1 和 sp2 两根指针指向obj,二者的 count 恢复为2。
  • 当 func 的参数是一个 shared_ptr<string> 对象的引用时,传参过程中发生 pass by reference,没有 sp2’ 生成,二者的 count 一直是 2。

1.3 reset()

  所谓 reset,就是“重置”。断开这根智能指针与当前内存的连接,把它连接到括号里那个对象的内存上。
【例 1】

int main(){
   
    shared_ptr<test> p1(new test(1));
    shared_ptr<test> p2 = make_shared<test>(2);
    cout << "p1的count = " << p1.use_count() << endl;
    cout << "p2的count = " << p2.use_count() << endl;

    p1.reset(new test(3));									//*1
    cout << "重置后p1的count = " << p1.use_count() << endl;

    shared_ptr<test> p3 = p1;
    cout << "p3的count = " << p1.use_count() << endl;

    p1.reset();
    cout << "置空后p1的count = " << p1.use_count() << endl;

    p2.reset();
    cout << "置空后p2的count = " << p2.use_count() << endl;

    cout << "此时p3的count = " << p3.use_count() << endl;
}

结果如下:

构造test对象 1
构造test对象 2
p1的count = 1
p2的count = 1
构造test对象 3
析构test对象 1
重置后p1的count = 1
p3的count = 2
置空后p1的count = 0
析构test对象 2
置空后p2的count = 0
此时p3的count = 1
析构test对象 3
  1. p1.reset(new test(3));,可以看到该行代码做了两件事:构造对象 3,并析构对象 1。p1 现在不再指向 test1 了,count 减少后,发现 test1 的引用计数变为 0 了,所以析构掉它。此时 p1 指向 test3。
  2. p1.reset();p2.reset();,可以理解为把这个指针指向 nullptr 了,它们原本指向的 test3 和 test2 因为 count 都没了,也随之被释放。一切智能指针调用没有参数的 reset() 后,它们都不再连接对象了,因此“它们的”引用计数全是0。
  3. 注意,reset() 只和智能指针关联的对象有关,这个智能指针现在什么都不指了,但是自身还存在,还可以接着指别的。
  4. 最后一行 test3 的析构,是由于函数结束,变量生命周期结束,由系统释放。每个函数与生俱来带有一个堆,函数结束,堆被释放,堆中数据清空。

【例 2】

//有一个自定义类型 Zoo,里面有一个int a
auto sp1 = make_shared<string>(new Zoo);
auto sp2(sp1);								//此时二者的count都是2
sp1.
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值