找回密码
 立即注册
首页 业界区 业界 【C++】智能指针

【C++】智能指针

俏襟选 2026-1-16 20:50:05
前言

学习C++智能指针。
指针(Pointer)就是一个变量,其存储的是另一个变量的内存地址,理解指针是掌握 C++ 内存管理、数组、对象以及底层操作的关键。
为什么使用指针

1.  动态内存管理:在运行时根据需要申请内存(使用 new 和 delete)。原生数组(如 int a[10])的大小在编译时就确定了,存储在栈(Stack)上。但很多时候,你并不知道程序运行过程中需要多少内存。

  • 按需分配:指针允许你在程序运行时使用 new 关键字在堆(Heap)上申请内存。
  • 生命周期控制:栈上的变量在函数结束时会自动销毁,而指针指向的堆内存可以跨越函数生命周期存在,直到你手动释放它。
2.  传递大型对象:通过指针传递参数(或引用)可以避免复制整个对象的开销,提高程序性能。
3.  实现复杂数据结构:如链表(每个节点通过指针指向下一个节点)、二叉树、图(父节点通过指针寻找子节点)等,必须依赖指针。
4.  底层操作:直接访问硬件或操作特定的内存区域。
为什么需使用智能指针

原生指针弊端

原生指针存在的问题:内存泄漏、悬空指针(重复释放)、野指针、所有权不清晰。
内存泄漏

用 new 分配了内存但忘记用 delete 释放!

  • 指针重定向
    C++
    1. int* p = new int(10); // 申请了内存 A
    2. p = new int(20);      // p 现在指向了内存 B,内存 A 的地址丢失了,再也找不回来。
    复制代码
  • 指针未释放
    1. void func() {
    2.     int* p = new int(10);
    3.     if (true) return; // 为真,函数直接返回,delete 被跳过!
    4.     delete p;
    5.     p = nullptr;
    6. }
    复制代码
  • 异常跳出: 程序运行中抛出异常,导致执行流直接跳转到 catch 块,没能执行到释放内存的代码。
  • 内存泄漏的后果,内存泄漏通常不会立刻导致程序崩溃,它的危害是渐进式的:

  • 性能下降 随着可用内存变少,操作系统会频繁进行内存交换(Swap),系统变得越来越卡。
  • 内存不足而崩溃 当内存被耗尽,新的 new 请求会失败,抛出异常,程序被迫中止。
  • 隐蔽性极强 在本地测试可能跑得好好的,但在服务器上连续运行几天甚至几周后,程序会无预兆地突然倒下
悬空指针(重复释放)

指向的内存已经被释放,但指针依然指向那个地址,重复释放指的是对同一块动态分配的内存进行多次释放操作
  1.         int* original = new int(100);
  2.     int* alias = original;  // 两个指针指向同一内存
  3.    
  4.     delete original;  // ✅ 释放内存
  5.     // 此时 original 和 alias 都成为悬空指针
  6.    
  7.     delete alias;     // ❌ 重复释放!同一内存再次释放
复制代码
野指针

指针未初始化,是指向“不可预知”内存区域的指针。它不是 nullptr,也不是指向有效的内存地址,它的值是随机的垃圾值
  1.     //定义一个局部指针变量却不初始化时,它在栈上的值是上一次程序运行留下的残余数据
  2.     int* p;        // 野指针!没有初始化,指向一个随机地址(如 0xCC123456)
  3.     *p = 100;      // 极其危险!你可能正在修改系统关键数据或其它变量
  4.     //指针跨越了作用域,返回局部变量的地址
  5.     int* getPointer() {
  6.         int x = 10;//x为栈上空间
  7.         return &x; // 错误!x 是局部变量,函数结束就被销毁了
  8.     }
  9.     int* p = getPointer(); // p 变成了野指针(或悬空指针)
复制代码
所有权不清晰

当这个指针不再被需要时,究竟该由谁来负责 delete 它
  1. // 谁该负责释放返回的这个指针?
  2. Data* fetchData() {
  3.     return new Data();
  4. }
  5. void process() {
  6.     Data* ptr = fetchData();
  7.     // 如果我忘了写 delete,内存泄漏
  8.     // 如果我 delete 了,但另一个地方也在用它,程序崩溃
  9. }
复制代码
智能指针优点


  • 自动化的生命周期管理(核心优点),智能指针遵循 RAII(资源获取即初始化)原则。不再需要手动写 delete。当智能指针对象在栈上被销毁(如函数返回、大括号结束、异常抛出)时,它会自动释放所指向的堆内存。
  • 防止野指针: 智能指针强制初始化,不会像原生指针那样默认指向随机地址。
  • 防止重复释放(Double Free): std::unique_ptr 通过禁止拷贝确保只有一个;std::shared_ptr 通过计数确保只在最后一次被使用时才释放。
  • 解决悬空指针: 使用 std::weak_ptr 可以在访问对象前先检查它是否还“活着”,从而避免访问已被释放的内存。
  • 所有权  std::unique_ptr独占、std::shared_ptr共享、std::weak_ptr观察。
  • 智能指针作为栈对象,即便发生异常,C++ 的“栈解旋(Stack Unwinding)”机制也会确保其析构函数被调用,从而安全地回收内存。这是编写健壮工业级代码的基础。
  • std::unique_ptr:具有零开销(Zero-overhead)。它在内存大小和运行速度上与原生指针完全一致,编译器会将其优化为最高效的机器码。
  • std::shared_ptr:虽然有引用计数的原子操作开销,但对于大多数业务逻辑来说,这种开销几乎可以忽略不计。
std::shared_ptr

shared_ptr 实际上包含两个指针:

  • 指向数据的指针:直接指向你申请的内存对象。
  • 指向控制块的指针:控制块是一个动态分配的内存区域,存放着:

    • 引用计数(Shared Count):有多少个 shared_ptr 拥有它。当你拷贝一个 shared_ptr 时,计数器加 1;当一个 shared_ptr 销毁时,计数器减 1。计数 > 0:资源保持有效。计数 = 0:最后一个“拥有者”负责调用 delete 销毁资源。
      引用计数的增加和减少是原子操作(使用类似 std::atomic 的机制)。这样可以保证在多线程环境下,多个线程同时拷贝或销毁指向同一个对象的 shared_ptr 时,计数器不会乱掉,从而避免内存泄漏或重复释放。
      它所指向的对象数据本身并不是线程安全的,多线程读写对象需要额外加锁。

    • 弱引用计数(Weak Count):有多少个 weak_ptr 正在观察它。
    • 自定义删除器(Deleter):如果需要特殊的释放逻辑。

[code]#include #include struct Widget {    Widget() { std::cout

相关推荐

2026-1-24 08:10:22

举报

2026-1-25 11:24:57

举报

喜欢鼓捣这些软件,现在用得少,谢谢分享!
7 天前

举报

昨天 10:48

举报

您需要登录后才可以回帖 登录 | 立即注册