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

C++智能指针详解 - 实践

请添加图片描述


文章目录

  • 引言
  • 1.智能指针的核心设计思想:RAII
    • 1.1.“RAII”原理
    • 1.2.智能指针的实现
  • 2.C++标准库中的智能指针
    • 2.1.`auto_ptr`
    • 2.2.`unique_ptr`:独占型智能指针
    • 2.3.`shared_ptr`:共享型智能指针
    • 2.4.`weak_ptr`:弱引智能指针
  • 3.智能指针的关键问题与解决方法
    • 3.1.`shared_ptr`的循环引用问题
    • 3.2.非new资源的释放(删除器)
    • 3.3.线程安全问题
  • 4.内存泄露的解决方案
    • 4.1.内存泄漏的定义与危害
    • 4.2.如何解决内存泄露问题
  • 5.C++智能指针的演进关系
  • 结语


引言

在C++动态内存管理过程中,如果用new分配内存,常常会因异常、代码的逻辑缺陷等,未能正常执行delete,导致内存泄漏,这种bug通常十分隐蔽,不好直接找出错误部分。而智能指针基于RAII设计思想,将资源管理与对象生命周期绑定实现内存的自动释放,大大降低了内存泄漏风险。


1.智能指针的核心设计思想:RAII

1.1.“RAII”原理

  • RAII(Resource Acquisition Is Initialization),即“资源获取即初始化”,他是一种管理资源的类的设计思想。其核心本质是:将资源(内存、文件指针等)的获取与对象的初始化绑定,资源的释放与对象的析构绑定。当对象被创建时,通过构造获取资源;对象生命周期结束时,自动调用析构,释放资源。无论程序正常执行还是异常退出,对象都会被销毁,避免了资源泄露的风险

1.2.智能指针的实现

代码示例:智能指针的简化实现

template<class T>class SmartPtr {public:// 构造时获取资源(RAII)SmartPtr(T* ptr) : _ptr(ptr) {}// 析构时释放资源(RAII)~SmartPtr() {cout << "delete[] " << _ptr << endl;delete[] _ptr;}// 重载运算符,模拟指针行为T& operator*() { return *_ptr; }T* operator->() { return _ptr; }T& operator[](size_t i) { return _ptr[i]; }private:T* _ptr; // 管理的原始指针};

通过使用智能指针,无需手动delete,对象销毁时资源会自动释放,即使发生异常也不会泄露。


2.C++标准库中的智能指针

C++便准库(<memory>头文件)中,提供了4种智能指针,分别适用于不同场景。

2.1.auto_ptr

auto_ptr<Date> ap1(new Date);auto_ptr<Date> ap2(ap1); // ap1管理权转移给ap2,ap1悬空// ap1->_year++; // 空指针访问,崩溃风险

2.2.unique_ptr:独占型智能指针

  • 特性:C++11推出,核心是 “独占资源”,禁止拷贝(仅支持移动语义move),确保同一时刻只有一个智能指针管理资源。
  • 适用场景:无需拷贝的单所有权场景。
  • 代码示例:
unique_ptr<Date> up1(new Date);// unique_ptr<Date> up2(up1); // 编译报错,禁止拷贝unique_ptr<Date> up3(move(up1)); // 支持移动,up1悬空(需谨慎使用)

2.3.shared_ptr:共享型智能指针

  • 特性:C++11推出,支持拷贝和移动,通过引用计数实现资源共享。
  • 核心原理:
    (1)维护一个堆上的引用计数变量_pcount,记录当前管理该资源的shared_ptr数量;
    (2)构造时:引用计数初始化为1;
    (3)拷贝时;引用计数+1;
    (4)析构时:引用计数-1。
  • 应用场景:需要多线程共享资源、多个对象共同管理同一资源的场景。
  • 代码示例:
shared_ptr<Date> sp1(new Date);shared_ptr<Date> sp2(sp1); // 拷贝,引用计数=2shared_ptr<Date> sp3 = make_shared<Date>(2024, 9, 11); // 推荐:直接初始化资源cout << sp1.use_count() << endl; // 输出2,查看引用计数
  • 注意:推荐使用make_shared<T>(args)构造,相比直接new,能减少内存分配次数,代码执行效率更高,且能减少内存泄露风险。

2.4.weak_ptr:弱引智能指针

  • 特性:与RAII无关,不管理资源,仅作为shared_ptr的辅助工具,用来绑定shared_ptr时不增加引用计数,不参与资源释放。
  • 核心用途:解决shared_ptr的引用循环问题(后文会提到)。
  • 关键接口:
    (1)expired():检查绑定的资源是否已被释放;
    (2)use_count():获取绑定shared_ptr的引用计数;
    (3)lock():返回一个shred_ptr指针(资源未被释放则指向该资源,已经释放则返回空对象),安全访问资源。

3.智能指针的关键问题与解决方法

3.1.shared_ptr的循环引用问题

当两个shared_ptr互相引用时,会形成循环依赖,导致引用计数永远无法减为0,资源无法被成功释放,造成内存泄露。

以双向链表为例:

struct ListNode {
int _data;
shared_ptr<ListNode> _next; // 互相引用shared_ptr<ListNode> _prev;};

初始化两个智能指针变量n1、n2:

shared_ptr<ListNode> n1(new ListNode); // 引用计数=1shared_ptr<ListNode> n2(new ListNode); // 引用计数=1

请添加图片描述
连接两个节点:

n1->_next = n2; // n2引用计数=2
n2->_prev = n1; // n1引用计数=2
// 析构n1和n2时,引用计数各减为1,无法释放资源

请添加图片描述
解决方案:将互相引用的对象改为weak_ptr,通过不增加引用计数,规避掉循环依赖关系。

struct ListNode {
int _data;
weak_ptr<ListNode> _next; // 弱引用,不增加计数weak_ptr<ListNode> _prev;};

3.2.非new资源的释放(删除器)

智能指针默认使用delete释放资源,若管理的是new[]分配的数组、文件指针等非new资源,直接使用会异常。此时需要我们自定义“删除器”,来确保资源能够正常释放。

常用删除器的实现方式:

// 1. 管理new[]数组(推荐:标准库特化版本)
unique_ptr<Date[]> up1(new Date[5]); // 自动用delete[]释放shared_ptr<Date[]> sp1(new Date[5]);// 2. 仿函数作为删除器template<class T>class DeleteArray {public:void operator()(T* ptr) { delete[] ptr; }};unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());// 3. lambda作为删除器(管理文件指针)shared_ptr<FILE> sp5(fopen("test.txt", "r"), [](FILE* ptr) {fclose(ptr);});

3.3.线程安全问题

shared_ptr的引用计数本身是线程安全的(标准库实现中使用原子操作),但多线程修改shared_ptr指向的对象时,会访问修改引用计数,此时就会存在线程安全问题,因此shared_ptr引用计数是需要加锁或者原子操作保证线程安全的。

代码示例:多线程修改shared_ptr管理的对象(需要加锁)

#include <iostream>#include <memory>#include <thread>#include <mutex>#include <functional>using namespace std;struct AA {int _a1 = 0;int _a2 = 0;AA() {cout << "AA 构造函数调用:" << this << endl;}~AA() {cout << "AA 析构函数调用:" << this << endl;}};int main() {shared_ptr<AA> p = make_shared<AA>();const size_t loop_count = 100000;mutex mtx;auto func = [&]() {for (size_t i = 0; i < loop_count; ++i) {//智能指针拷贝会++计数shared_ptr<AA> copy(p);unique_lock<mutex> lk(mtx);copy->_a1++;copy->_a2++;}};thread t1(func);thread t2(func);t1.join();t2.join();cout << "最终 _a1 = " << p->_a1 << endl;cout << "最终 _a2 = " << p->_a2 << endl;cout << "当前shared_ptr引用计数 = " << p.use_count() << endl;return 0;}

4.内存泄露的解决方案

4.1.内存泄漏的定义与危害

4.2.如何解决内存泄露问题

  1. 优先使用智能指针管理资源,遵循RAII思想;
  2. 使用工具辅助检测:Linux下的内存泄漏检测工具和Windows下的内存检测工具;
  3. 尽量避免手动new/delete,若必须使用,一定要确保成对出现,且异常场景下能执行释放。

5.C++智能指针的演进关系

  • C++98:推出auto_ptr,存在严重设计缺陷;
  • Boost库:提出scoped_ptr(对应 C++11 的unique_ptr)、shared_ptrweak_ptr,成为 C++11 标准实现更多智能指针的参考;
  • C++11:大量借鉴Boost库中的智能指针设计,正式推出unique_ptrshared_ptrweak_ptr,进一步完善智能指针体系。

结语

智能指针作为 C++11 的内存管理核心工具,彻底革新了动态资源管理方式:无需手动调用new/delete,通过RAII机制自动绑定资源生命周期,让代码更安全可靠;多样的所有权模型(独占 / 共享 / 弱引用)能适配不同场景需求,规避内存泄漏与重复释放,显著降低了开发风险。

http://www.jsqmd.com/news/285434/

相关文章:

  • 汽车自动驾驶的太阳光模拟应用研究 - 详解
  • 学术降重必备:AI生成论文工具精选
  • 机器人落地“首台套”补贴,到底指什么?
  • SQLModel 全面教程:常用 API 串联与实战指南
  • SQLModel 全面教程:常用 API 串联与实战指南
  • 论文写作神器:十大AI辅助工具榜单
  • TAOCP 1.2.1部分习题 - Ghost
  • 苏州 Linux服务器 无法进入系统(Grub Rescue)
  • 2026年制冷机/气体制冷机/冷热一体机 优选榜单公布
  • LLM知识随笔(二)--BERT
  • AIGC论文助手:10款智能写作工具盘点
  • 显示器的宽高比一般是多少?什么是屏幕分辨率?常讲的2K 、4K和8K电视是什么含义?
  • No.9 监理工作的组织和规划
  • 吐血推荐!10款AI论文工具测评,本科生写论文太省力了
  • AS721低功耗交换芯片 搭CS5801互传HDMI DP/hdmi to dp双向互传
  • 十大AI论文神器:智能降重与高效写作指南
  • 论文降重利器:AI生成工具Top10推荐
  • 学长亲荐2026研究生必用AI论文网站TOP9:开题报告文献综述神器
  • 如何安全抓取SoundCloud数据用于音频 AI 模型训练?
  • 云服务器部署项目
  • 苏州服务器系统崩溃/卡在启动界面
  • Ozon还是Joom?俄罗斯电商新手的平台选择全解析
  • 2026 年 GEO 系统优化推广公司排名公布:TOP3 权威测评来了!
  • 揭秘!2026 年 GEO营销 系统优化推广公司/服务商 TOP3(权威评测)
  • Educational Codeforces Round 84 部分题解
  • 数据结构排序算法详解(5)——非比较函数:计数排序(鸽巢原理)及排序算法复杂度和稳定性分析 - 指南
  • AI开发-python-langchain框架(1-4动态少样本提示)
  • 揭秘!2026 年百度竞价广告开户代运营推广公司 TOP3(权威评测)
  • 【性能测试】2_Locust _Locust基本使用
  • 【CDA干货】财务分析一定要学会的2个模型:杜邦分析法+UE模型