C++ - 智能指针

C++ 智能指针是现代 C++ 编程中用于管理动态内存和资源的核心工具。它们基于 ‌RAII‌(Resource Acquisition Is Initialization,获取资源即初始化)惯用法,确保在对象生命周期结束时自动释放资源,从而有效防止内存泄漏并提高程序的异常安全性。

1. 核心概念与优势

  • ‌自动内存管理‌:智能指针是栈上分配的类模板对象,它们封装了堆上分配的原始指针。当智能指针超出作用域时,其析构函数会自动被调用,进而释放所管理的内存或资源。
  • ‌异常安全‌:即使代码执行过程中抛出异常,栈展开机制也会确保智能指针的析构函数被执行,从而避免资源泄漏。
  • ‌替代原始指针 ‌: 在现代 C++ 中,应尽量避免直接使用 new 和 delete。原始指针仅应用于范围极小、性能极度敏感且不涉及所有权转移的场景。

2. 主要类型

C++ 标准库(<memory> 头文件)提供了三种主要的智能指针,分别适用于不同的所有权场景:

(1) std::unique_ptr ------ 独占所有权
(2) std::shared_ptr ------ 共享所有权
(3) std::weak_ptr ------ 弱引用(观察权)
cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
    /* 1、********************    unique_ptr    ***************  */
    //独占所有权
    std::unique_ptr<int> ptr = std::make_unique<int>(12);
    std::cout << "ptr value: " << *ptr << std::endl;

    //转移所有权,此时ptr为空,ptr2拥有资源
    //ptr2接管了堆上int对象的所有权,ptr被重置为nullptr(空指针),它不再管理任何资源
    std::unique_ptr<int> ptr2 = std::move(ptr);

    //*ptr: 解引用空指针,程序崩溃
    //ptr->method(); 通过空指针调用成员函数,程序崩溃
    //可以判断状态 if(ptr) if(ptr == nullptr)
    //重新赋值 ptr = std::make_unique<int>(100);
    if ( ptr == nullptr )
    {
        std::cout << "ptr is now null" << std::endl;
    }

    if (ptr2)
    {
        std::cout << "ptr2 value: " << *ptr2 << std::endl;
    }

    /* 2、********************    shared_ptr    ***************  */
    //共享所有权
    //引用计数:内部维护一个引用计数(use_count).每增加一个指向同一对象的shared_ptr,计数加1;每销毁一个计数减1
    //自动释放:当引用计数降为0时,自动删除所管理的对象
    //线程安全:引用计数的增减是原子操作,因此多个线程可以安全地拷贝或销毁指向同一个对象的shared_ptr
    //注意:这并不意味着所管理的对象本身是线程安全的
    //适用场景:多个所有者需要共享同一资源,且无法确定哪个所有者最后销毁资源
    //推荐使用std::make_shared<T>(args...) 相比直接new,make_shared只需一次内存分配(同时分配控制块和对象)
    std::shared_ptr<int> shptr = std::make_shared<int>(22);
    std::shared_ptr<int> shptr2 = shptr;  //共享所有权,引用计数变成2

    std::cout << "use count: " << shptr.use_count() << std::endl;  //输出2
    shptr2.reset();  //ptr2 释放所有权,引用计数变为 1
    std::cout << "use count after reset: " << shptr.use_count() << std::endl; //输出1


    /* 3、********************    weak_ptr    ***************  */
    //弱引用,观察权
    //不拥有资源:weak_ptr 不会增加引用计数,因此不会影响对象的生命周期
    //临时访问:必须通过.lock()方法尝试提升为shared_ptr才能访问对象。如果对象已被销毁,lock()返回空的shared_ptr
    //适用场景:解决循环引用,当两个对象互相持有对方的shared_ptr时,引用计数永远无法归零,导致内存泄露。
    //将其中一方改为weak_ptr可打破循环
    //缓存或观察者模式:需要访问对象但不希望阻止其被销毁

    return 0;
}

3. 控制块(Control Block)

std::shared_ptr对象本身并不直接包含引用计数变量。相反、它内部通常包含两个指针(在64位系统上各占8字节)

资源指针(Resource Pointer): 指向实际管理的对象

控制块指针(Control Block Pointer):指向一个动态分配的控制块。

注意:std::shared_ptr的大小通常时原始指针的两倍(因为存了两个指针),而引用计数的操作需要通过控制块指针间接访问。

对象销毁:当强引用计数(strong_count)变为0时,控制块中存储的删除器会被调用,从而销毁实际管理的对象。

控制块销毁:只有当强引用计数和弱引用计数(weak_count)同时变为0时,控制块本身的内存才会被释放。

这就是为什么即使所有shared_ptr都销毁了,如果还存在weak_ptr,实际对象的内存虽然被释放了,但控制块的内存依然保留,以便weak_ptr能安全地查询对象是否存活。

std::shared_ptr 的引用计数操作(增加和减少)使用的是‌原子操作‌ (Atomic Operations)

这意味着多个线程可以同时拷贝或销毁指向同一对象的 shared_ptr,而不会导致数据竞争或引用计数错误。

‌重要限制 ‌:原子性仅保证‌引用计数本身 ‌的安全,‌不保证 ‌所指向对象的数据访问安全。如果多个线程通过 shared_ptr 读写同一个对象的内容,仍需使用互斥锁 (Mutex) 等同步机制。

相关推荐
第九序言T2 小时前
Python 数据处理进阶(十八)批量处理同结构文件:一个文件夹一次跑完
开发语言·python·自动化·pandas·数据处理
Sakura_j_h_2 小时前
EasyEDA PCB 快速设计Skills
c++·python·github
龙亘川3 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
程序喵大人3 小时前
【C++入门】内存模型与资源管理 - 09 shared_ptr 和 weak_ptr 解决共享,也带来代价
开发语言·c++·资源管理·智能指针·内存模型
Raas1004 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
专注仿真5 小时前
线性回归和逻辑回归算法详解
开发语言·人工智能·python·大模型训练·控制算法
jimy15 小时前
C++ 中的 `T&&`:右值引用与转发引用
开发语言·c++
呆呆虫126 小时前
用 orjson 加速 SerpBase 响应解析:基准对比与注意事项
开发语言·php
心疼你的一切6 小时前
Python自动发邮件实战定时发报表
大数据·开发语言·python
Wang's Blog7 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友