c++学习笔记——智能指针

前言

  1. 智能指针是可以自动回收内存的指针

  2. 共享智能指针 shared_ptr,可以多个共享同一块堆内存

    独享智能指针 unique_ptr,只能独有一块堆内存

    弱引用智能指针 weak_ptr,也是一种共享型,可以视作标准库给 shared_ptr 打的补丁

  3. 智能指针和裸指针不要混用

  4. 本文使用 Visual Studio 2022 进行测试

shared_ptr

可以给堆内存加一个引用计数,即指向它的指针数,如果为 0 就释放

构造
  1. 用地址初始化(new 表达式,指针)

    • 注:
      1. 不建议用指针初始化,因为智能指针自动释放,如果手动释放了指针会导致重复释放
        • 简单说,当使用智能指针时,不允许操控其内部的指针本体
      2. 参数支持地址,但你可千万别把地址当参数,拿内存当玩具
    std::shared_ptr<int> shared_i_ptr(new int(100));
    
  2. 用专门实现的 make_shared,更高效

    std::shared_ptr<int> shared_i_ptr = std::make_shared<int>(100);
    
  3. 拷贝构造函数(用于共享)

引用计数

use_count() 显示当前与自己指向同一个目标的 shared_ptr 总数

reset() 无参数表示取消对该堆内存的引用,会修改引用计数,所以不能在 const 条件下调用

std::shared_ptr<int> sharedI1 = std::make_shared<int>(100);
std::shared_ptr<int> sharedI2(sharedI1);
std::cout << sharedI1.use_count();	// 2

sharedI1.reset();
std::cout << sharedI1.use_count();	// 1
实现传统指针操作
cout << *sharedI1 << endl;
sharedP -> func();	// 假设有成员函数
其他函数
sharedP.unique();	// 是否独占
sharedP.reset(地址);	// 改变指针指向和原对象的引用计数,无参则置空,因为底层是2个指针所以细节略有说法
sharedP = sharedP2;	// 也可以赋值改变指向

sharedI.swap(sharedI2);	// 交换指向内存,推荐用,问就是标准库实现的,有优化
std::swap(sharedI, sharedI2);	// 等效的宏提供的函数

sharedP.get();	// 返回原始指针,只是保留访问,但非必要情况都默认废止了
其他语法
  • 指向数组,也是少数需要借助原始指针的场合(不清楚 C++17/20 后的标准有没有改)

    std::shared_ptr<int> shared(new int[100]());
    shared.get()[5];	// 获取指针再[ ],暂不支持直接使用[ ]
    
  • 作为参数,shared_ptr 固定为 2 个指针大小(32/64 位系统中分别占 8/16 字节)所以可以直接传值

    关于 shared_ptr 为何是 2 个指针,以下是来自 ChatGPT 的解释:

    1. 内存指针:
      • 内存指针指向实际的动态分配内存,其中存储着数据或对象。
      • 它是 shared_ptr 的主要指针,用于管理实际的资源。当 shared_ptr 创建时,它会持有对这个内存的引用,允许你访问和操作内存中的数据。
      • 内存指针通常是你最关心的部分,因为它指向你要管理的数据或对象。
    2. 控制块指针:
      • 控制块指针指向一个控制块(reference control block),这个控制块包含有关所指内存的元数据和引用计数信息。
      • 控制块用于管理共享相同内存的所有 shared_ptr 实例。它通常包含以下信息:
        • 引用计数:表示有多少个 shared_ptr 实例共享相同的内存。
        • 指向内存的指针:实际指向内存的指针。
        • 自定义删除器:用于在引用计数归零时释放内存的自定义函数对象(如果提供了)。
        • 其他元数据:如类型信息等。
    • 控制块起到管理和协调作用,确保在多个 shared_ptr 实例之间正确跟踪引用计数和在引用计数归零时释放内存。这使得多个 shared_ptr 实例可以共享相同的内存,而不会引起资源泄漏,因为内存的释放是由控制块来处理的。

    • 所以,总结来说,内存指针是直接指向实际资源的指针,而控制块指针指向一个控制块,这个控制块负责跟踪共享资源的元数据和引用计数信息。这种组合允许 shared_ptr 实现了内存资源的共享和自动释放,以确保资源安全地管理。

weak_ptr

专用于弥补 shared_ptr 缺陷(循环引用)的指针,特点是不会影响引用计数

构造和计数
std::shared_ptr<int> sharedI = std::make_shared<int>(100);
std::cout << sharedI.use_count() << std::endl;	// 1

std::weak_ptr<int> weakI(sharedI);
std::cout << weakI.use_count() << std::endl;	// 1

**shared_ptr 的循环引用:**假设堆上分配了两个互相引用的 shared_ptr,栈上指针销毁后,堆上指针引用计数仍为 1,造成泄漏

class B;	// C++ 特有的编译需要先有声明,成员变量使用指针也是因为指针大小固定,不需要先编译类来确定大小
class A{
public:
    std::shared_ptr<B> sharedB;		// 可以改成 weak_ptr<B>
};
class B{
public:
    std::shared_ptr<A> sharedA;
};

int main(){
    std::shared_ptr<A> shareA = std::make_shared<A>();
	std::shared_ptr<B> shareB = std::make_shared<B>();

	shareA->sharedB = shareB;
	shareB->sharedA = shareA;	// 两块堆内存互相指
}	// 栈指针销毁,堆内存计数始终为1,彼此都无法释放,泄漏!

因此设计了一种不会影响引用计数的 weak_ptr,用且几乎仅用于此类情况

对于以上情况,将至少一个堆对象成员指针换成 weak_ptr,能保证栈指针销毁后,其中一块堆内存计数为 0

此外,weak_ptr 的使用基本等同 shared_ptr

unique_ptr

独占特化的智能指针,当对象不需要被多个指针共享时,使用 unique_ptr 可以降低消耗

因为不需要考虑共享和计数问题,所以 unique_ptr 只有一个指针

  • unique_ptr 禁止了拷贝构造和赋值运算符,因为破坏了独占

  • unique_ptr 允许移动构造和赋值,因为移动没有影响独占

    std::unique_ptr<int> uniqueI = std::make_unique<int>(100);
    std::unique_ptr<int> uniqueI2 = std::move(uniqueI);
    

unique_ptr 可以作为右值转为 shared_ptr,反之共享则不能转为独占。这个属于是提供容错用的,不用管

std::shared_ptr<int> sharedI = std::make_unique<int>(100);

如何上手智能指针

省流:优先级 unique_ptrshared_ptrweak_ptr → 传统指针

  1. 除了以下情况,都可以用智能指针,因为本身就是为了让指针这远古玩意跟上新版本而加的套壳

    • 规定使用 C 指针的函数:

      • 网络传输函数,如 Windows 下的 sendrecv

        image-20231019005041536

      • C 语言库函数,如文件操作(但如今支持智能指针的 C++ 函数也在平替中)

    • 指向数组:

      智能指针对数组的支持并不好,指操作不便

  2. 优先使用 unique_ptr

    需要共享时使用 shared_ptr

    在此基础上如果循环引用,则改为 weak_ptr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值