当前位置: 首页 > news >正文

【C++】浅谈智能指针

文章目录

    • 1. 指针基础
    • 2. 智能指针的本质与实现原理
    • 3. 智能指针类型
      • 3.1 std::unique_ptr(独占所有权指针)
      • 3.2 std::shared_ptr(共享所有权指针)
      • 3.3 std::weak_ptr(弱引用指针)
    • 4. 关键知识点
      • 4.1 创建智能指针
      • 4.2 所有权转移
    • 5. shared_ptr的所有权共享机制
    • 6. 循环引用问题及解决方案
      • 6.1 什么是循环引用?
      • 6.2 基本示例
      • 6.3 循环引用的工作原理
      • 6.4 解决方案:使用std::weak_ptr
    • 7. weak_ptr的计数方式与内存分配
      • 7.1 weak_ptr真的不计数吗?
      • 7.2 内存分配方式
      • 7.3 控制块内容
      • 7.4 生命周期管理
      • 7.5 可视化示例
      • 7.6 形象比喻

1. 指针基础

指针全名为指针变量,是C++中用于存储内存地址的变量。计算机在存储数据时采用有序存放的方式,为了能够准确访问每个数据的位置,需要使用地址来区分,指针变量就是专门用来存放这些地址的变量。

2. 智能指针的本质与实现原理

智能指针本质上是封装了原始C++指针的类模板,是为了确保动态内存安全性而产生的。其实现原理是通过一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。

智能指针是C++11引入的最重要特性之一,用于自动管理动态分配的内存,防止内存泄漏和悬空指针问题。它们遵循RAII(Resource Acquisition Is Initialization)原则,确保资源在不再需要时被正确释放。

3. 智能指针类型

C++标准库提供了三种主要的智能指针:

3.1 std::unique_ptr(独占所有权指针)

  • 特点:独占所指向的对象,不允许复制,但可以移动
  • 用途:用于管理独占所有权的资源,替代原始指针的首选
  • 内存开销:几乎为零(通常与原始指针大小相同)

3.2 std::shared_ptr(共享所有权指针)

  • 特点:多个shared_ptr可以共享同一个对象的所有权,使用引用计数
  • 用途:用于需要多个所有者共享同一资源的场景
  • 内存开销:需要存储引用计数(通常是指针大小的两倍)

3.3 std::weak_ptr(弱引用指针)

  • 特点:不增加引用计数,用于解决shared_ptr的循环引用问题
  • 用途:观察shared_ptr管理的对象,但不拥有所有权
  • 内存开销:与shared_ptr类似

4. 关键知识点

4.1 创建智能指针

优先使用std::make_uniquestd::make_shared

//(C++14起)
auto ptr1 = std::make_unique<int>(42);
auto ptr2 = std::make_shared<std::string>("Hello");// 传统方式
std::unique_ptr<int> ptr3(new int(42));
std::shared_ptr<std::string> ptr4(new std::string("Hello"));

4.2 所有权转移

// unique_ptr 的所有权转移
std::unique_ptr<int> source = std::make_unique<int>(42);
std::unique_ptr<int> destination = std::move(source); // source 变为空// shared_ptr 的所有权共享
std::shared_ptr<int> shared1 = std::make_shared<int>(42);
std::shared_ptr<int> shared2 = shared1; // 引用计数增加

5. shared_ptr的所有权共享机制

当多个shared_ptr共享同一对象时:

std::shared_ptr<int> shared1 = std::make_shared<int>(42);
std::shared_ptr<int> shared2 = shared1; // 共享同一对象

底层机制详解

  1. 第一行:std::make_shared<int>(42)

    • 在堆上分配内存并构造一个值为42的int对象
    • 同时创建一个控制块,其中包含引用计数(初始为1)和其他管理信息
    • shared1包含两个指针:一个指向int对象,一个指向控制块
  2. 第二行:shared2 = shared1

    • shared2复制了shared1的两个指针:指向同一int对象和同一控制块
    • 控制块中的引用计数增加(从1变为2)

6. 循环引用问题及解决方案

6.1 什么是循环引用?

循环引用发生在两个或多个对象通过shared_ptr相互引用时,形成一个闭环,导致它们的引用计数永远不会降为零,从而无法被自动销毁。

6.2 基本示例

#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::shared_ptr<A> a_ptr;~B() { std::cout << "B destroyed\n"; }
};int main() {auto a = std::make_shared<A>();auto b = std::make_shared<B>();// 形成循环引用a->b_ptr = b;b->a_ptr = a;std::cout << "A use count: " << a.use_count() << std::endl;std::cout << "B use count: " << b.use_count() << std::endl;return 0;// 程序结束时不会打印"A destroyed"和"B destroyed"
}

6.3 循环引用的工作原理

  1. 创建a时,A对象的引用计数为1
  2. 创建b时,B对象的引用计数为1
  3. 当执行a->b_ptr = b时:
    • B对象的引用计数增加为2
    • 现在有两个shared_ptr指向B对象:b和a->b_ptr
  4. 当执行b->a_ptr = a时:
    • A对象的引用计数增加为2
    • 现在有两个shared_ptr指向A对象:a和b->a_ptr
  5. 当main函数结束时:
    • 局部变量a和b被销毁,它们的引用计数减1
    • A对象的引用计数变为1(来自b->a_ptr)
    • B对象的引用计数变为1(来自a->b_ptr)
    • 由于引用计数不为零,A和B对象都不会被销毁
    • 但没有任何外部指针可以访问这些对象了,导致内存泄漏

6.4 解决方案:使用std::weak_ptr

std::weak_ptr是一种不控制对象生命周期的智能指针,它指向一个由shared_ptr管理的对象,但不会增加引用计数。

修复循环引用

#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::weak_ptr<A> a_ptr; // 使用weak_ptr而不是shared_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a; // weak_ptr不会增加引用计数std::cout << "A use count: " << a.use_count() << std::endl;std::cout << "B use count: " << b.use_count() << std::endl;return 0;// 程序结束时会打印"B destroyed"和"A destroyed"
}

7. weak_ptr的计数方式与内存分配

7.1 weak_ptr真的不计数吗?

  • weak_ptr不参与强引用计数:不会阻止对象被销毁
  • weak_ptr参与弱引用计数:用于管理控制块的生命周期

7.2 内存分配方式

  • 使用std::make_shared:对象和控制块在同一内存块中分配
  • 直接使用new:对象和控制块分开分配

7.3 控制块内容

控制块包含:

  • 强引用计数
  • 弱引用计数
  • 其他管理信息

7.4 生命周期管理

  • 对象生命周期:当强引用计数变为0时,对象被销毁(调用析构函数)
  • 控制块生命周期:当强引用计数和弱引用计数都变为0时,控制块被释放

7.5 可视化示例

时间线:
1. shared_ptr 创建: [控制块: strong=1, weak=0] → [对象存在]
2. weak_ptr 创建: [控制块: strong=1, weak=1] → [对象存在]
3. shared_ptr 重置: [控制块: strong=0, weak=1] → [对象销毁!]
4. weak_ptr 检查: 对象已销毁,但控制块存在
5. weak_ptr 销毁: [控制块: strong=0, weak=0] → [控制块销毁]

7.6 形象比喻

想象一个热气球(对象)和它的锚点(控制块):

  1. shared_ptr是抓住气球绳子的人
    • 每个shared_ptr都用力拉着绳子(强引用计数+1)
    • 只要还有人拉着,气球就不会飞走(对象不会被销毁)
  2. weak_ptr是旁边看气球的人
    • 他们只观察气球,并不拉绳子(不增加强引用计数)
    • 但他们需要知道这个锚点位置是否存在(弱引用计数+1)

发生了什么?
当最后一个shared_ptr松手(强引用数归零):

  • 气球立刻飞走销毁(对象内存被释放)
  • 但锚点还在地上(控制块还在)
  • 因为还有weak_ptr观察者需要知道"这里曾经有个气球"

当最后一个weak_ptr也离开:

  • 锚点被移除(控制块内存最终释放)

weak_ptr不阻止对象被销毁(不计强引用数),但需要控制块活着来告诉你对象是否还存在(计弱引用数)。这就是它既能解决循环引用,又能安全检查对象状态的原因


本文基于学习笔记整理


文章转载自:

http://P5goDENl.xykst.cn
http://YFhQCpOH.xykst.cn
http://4HZ4GX0H.xykst.cn
http://b0HXslJj.xykst.cn
http://B0aTyzVA.xykst.cn
http://HgLrXBWS.xykst.cn
http://Kwv44pEm.xykst.cn
http://CuzHYXeA.xykst.cn
http://63nPoc2b.xykst.cn
http://S5oGEU9w.xykst.cn
http://7yWarSmk.xykst.cn
http://Jupq9DQl.xykst.cn
http://iVSEri39.xykst.cn
http://mAnjaU14.xykst.cn
http://QTRz5Esk.xykst.cn
http://P4RBci5N.xykst.cn
http://fbaV1wIh.xykst.cn
http://ywlaagTr.xykst.cn
http://BvZXIk1c.xykst.cn
http://ShIOaNK8.xykst.cn
http://d2sMhrzO.xykst.cn
http://xyOAmUYx.xykst.cn
http://uN78cE9l.xykst.cn
http://2uAmW94J.xykst.cn
http://Ie6i51ut.xykst.cn
http://OAg1DYxu.xykst.cn
http://TcIEinTB.xykst.cn
http://qf2b3uxt.xykst.cn
http://dBYh8Khk.xykst.cn
http://oH07ArYi.xykst.cn
http://www.dtcms.com/a/385629.html

相关文章:

  • 第三章 神经网络入门笔记:从概念到实践全解析
  • 20250915在荣品RD-RK3588-MID开发板的Android13系统下使用TF卡刷机
  • 四元论的正确性数学原理
  • 你的第一个AI项目部署:用Flask快速搭建模型推理API
  • MyBatis-相关知识点
  • 【Nginx开荒攻略】Nginx配置文件语法规则:从基础语法到高级避坑指南
  • 【系统分析师】2024年下半年真题:论文及解题思路
  • Linux 标准输入 标准输出 标准错误
  • 【减少丢帧卡顿——状态管理】
  • pytest 常用方法介绍
  • 牛客周赛 Round 109 (小红的直角三角形
  • 【C++实战⑬】解锁C++文件操作:从基础到实战的进阶之路
  • 股票进阶之成交量买卖法
  • 【LangChain指南】Prompt templates
  • CSS基础 - 选择器备忘录 --笔记5
  • Vue-30-利用Vue3大模型对话框设计之切换主题时显示对应的session列表
  • 全光谱 LED 太阳光模拟器的原理
  • 权限更改centos中系统文件无法创建文件夹,使用命令让普通用户具备操作文件夹
  • 【WebGIS】Vue3使用 VueLeaflet + 天地图 搭建地图可视化平台(基础用法)
  • 69-SQLite应用
  • Day06 双指针扫描 | 11. 盛最多水的容器
  • LeetCode 刷题【77. 组合、78. 子集、79. 单词搜索】
  • Jenkins 构建清理策略:自带功能 vs Discard Old Build 插件,全场景实操指南
  • DevOps历程-Gogs的安装与部署
  • FreeRTOS 任务静态创建与句柄详解
  • 嵌入式音视频开发——RTMP协议详解
  • 每日一题(6)
  • 信号量主要API及综合应用
  • 【开题答辩全过程】以 B站用户视频喜好倾向数据分析系统为例,包含答辩的问题和答案
  • ARM架构学习6.2——中断理解