news 2026/3/6 4:02:46

智能指针深度解析:C++内存管理的神奇利器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
智能指针深度解析:C++内存管理的神奇利器

1.智能指针的引入

观察下列程序,正常情况下,程序new的对象我们能正常释放,但是当抛异常出现后,后⾯的delete没有得到执行,所以内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出(将注释部分取消即可)。

代码语言:javascript

AI代码解释

double Divide(int a, int b) { // throw "Divide by zero condition!"; if (b == 0) { throw "Divide by zero condition!"; } else { return (double)a / (double)b; } } void func() { int * arr1= new int[10]; int * arr2 = new int[10]; /*try {*/ int x; int y; cin >> x >> y; cout << Divide(x, y) << endl; /*}*/ //catch (...) { // cout << "delete []" << arr1 << endl; // cout << "delete []" << arr2 << endl; // delete[] arr1; // delete[] arr2; // throw;//异常重新抛出 //} cout << "delete []" << arr1 << endl; delete[] arr1; cout << "delete []" << arr2 << endl; delete[] arr2; } int main() { try { func(); } catch (const char* errmsg) { cout << errmsg << endl; } catch (const exception& e) { cout << e.what() << endl; } catch (...) { cout << "未知异常" << endl; } return 0; }

但是因为new本身也可能抛异常,两个new和Divide同时有问题呢,那我们就要套很多个try---catch语句,就很麻烦,因此C++引入了智能指针这位重量级人物。

2.RAII与智能指针的设计思路

在C++编程中,资源管理是一个至关重要的问题,尤其是涉及动态内存分配、文件操作、网络连接和线程同步等场景时。如果资源没有被正确释放,就会导致内存泄漏资源占用过长的问题。为了解决这个问题,RAII(Resource Acquisition Is Initialization,资源获取即初始化)设计思想应运而生,并成为智能指针设计的核心理念之一。


2.1 RAII(资源获取即初始化)
2.1.1 RAII 的核心概念

RAII 是一种管理资源的 C++ 编程思想,其核心原则是利用对象的生命周期来管理资源的申请与释放,确保资源不会被错误地释放或泄露。RAII 主要包含以下几个关键点:

  1. 在对象构造时获取资源(即资源的获取和初始化绑定在一起)。
  2. 在对象析构时释放资源(当 RAII 对象离开作用域时,资源会被自动释放)。
  3. 资源在对象生命周期内始终保持有效,不会因为异常或程序流程问题导致资源泄漏。
2.1.2 RAII 的典型应用

RAII 主要用于管理需要手动释放的资源,如:

  • 动态内存管理(new/delete, malloc/free)
  • 文件操作(文件打开/关闭)
  • 互斥锁(lock/unlock)
  • 网络连接(连接/断开)
2.1.3 代码示例:RAII 管理文件句柄

代码语言:javascript

AI代码解释

#include <iostream> #include <fstream> class FileGuard { private: std::fstream file; public: // 构造函数打开文件 FileGuard(const std::string& filename) { file.open(filename, std::ios::out); if (!file.is_open()) { throw std::runtime_error("文件打开失败"); } } // 提供文件流操作 std::fstream& get() { return file; } // 析构函数关闭文件 ~FileGuard() { if (file.is_open()) { file.close(); std::cout << "文件已关闭\n"; } } }; int main() { try { FileGuard fg("example.txt"); fg.get() << "Hello, RAII!"; } catch (const std::exception& e) { std::cerr << "异常:" << e.what() << std::endl; } return 0; }

代码解析:

  • FileGuard类在构造时自动打开文件,在析构时自动关闭文件
  • 即使main函数中抛出异常,FileGuard也能保证文件被正确关闭,避免资源泄漏。
  • 通过RAII 方式,不需要手动close()文件,降低了出错的可能性。

3.1 智能指针的设计思路

C++ 的智能指针(Smart Pointer)RAII 思想的典型应用,用于管理动态分配的内存,避免手动new/delete可能导致的内存泄漏。

3.1.1 智能指针的额外需求

相比于 RAII 的一般资源管理,智能指针除了需要在析构时释放资源之外,还需要:

  • 模仿原生指针的行为,即可以像指针一样访问和操作对象。
  • 提供安全的引用计数(shared_ptr),支持多个智能指针共享同一块内存。
  • 支持独占管理(unique_ptr),避免多个指针同时管理同一块内存。
3.1.2 智能指针的核心机制

智能指针类通过重载运算符来模拟原生指针的行为:

  1. operator*允许解引用智能指针,访问内部对象。
  2. operator->允许使用ptr->成员语法访问内部对象。
  3. 构造函数负责初始化并绑定资源。
  4. 析构函数负责在合适的时机释放资源。
3.1.3 代码示例:简单实现一个智能指针

代码语言:javascript

AI代码解释

#include <iostream> // 自定义智能指针 template<typename T> class SmartPointer { private: T* _ptr; public: explicit SmartPointer(T* ptr = nullptr) : _ptr(ptr) {} ~SmartPointer() { delete _ptr; std::cout << "资源已释放\n"; } // 重载 * 和 -> 访问对象 T& operator*() { return *_ptr; } T* operator->() { return _ptr; } }; class Test { public: void show() { std::cout << "智能指针测试\n"; } }; int main() { SmartPointer<Test> sp(new Test()); sp->show(); // 使用 -> 访问 Test 的方法 return 0; // 离开作用域时,资源自动释放 }

代码解析:

  • SmartPointer通过重载operator*operator->,使其行为类似于普通指针
  • 构造时传入new分配的对象,析构时自动释放内存,避免手动delete带来的错误。

接下来我们对引入的例子进行修改(这里也是简单实现一个智能指针)

代码语言:javascript

AI代码解释

class SmartPtr { public: //模仿RAII SmartPtr(T* ptr) :_ptr(ptr) {} ~SmartPtr() { cout<< "delete " << _ptr << endl; delete[] _ptr; } //重载运算符,模拟指针行为,方便访问资源 T* operator ->() { return _ptr; } T& operator *() { return *_ptr; } T& operator [](int index) { return _ptr[index]; } private: T* _ptr; }; double Divide(int a, int b) { // throw "Divide by zero condition!"; if (b == 0) { throw "Divide by zero condition!"; } else { return (double)a / (double)b; } } void func() { SmartPtr<int> sp1=new int[10]; SmartPtr<int> sp2=new int[10]; SmartPtr<pair<int, int> > sp3=new pair<int, int>[10]; for (int i = 0; i < 10; i++) { sp1[i] = sp2[i] = i; } for (int i = 0; i < 10; i++) { cout << sp1[i] << " "; } int x, y; cin >> x >> y; cout << Divide(x, y) << endl; }

3.C++ 标准库智能指针的使用

在 C++ 标准库中,智能指针位于<memory>头文件中,因此包含<memory>头文件后就可以使用智能指针。智能指针的设计目标是自动管理动态分配的资源,避免手动new/delete可能导致的内存泄漏和悬空指针问题。

除了weak_ptr之外,其他标准库智能指针都符合RAII(资源获取即初始化)原则,并且支持像原生指针一样访问资源。不同类型的智能指针,主要的区别在于拷贝语义的处理方式。


1.auto_ptr(C++98,已废弃)
  • auto_ptrC++98设计的智能指针,其拷贝行为存在严重问题:
    • 在拷贝时,它会转移资源的管理权,导致原来的auto_ptr变成悬空指针,容易引发访问非法内存的错误。
    • 由于这个设计缺陷,C++11引入新的智能指针后,强烈建议不要使用auto_ptr,甚至在 C++17被移除
  • 许多公司在 C++11 之前,就已经明确禁止使用auto_ptr了。

2.unique_ptr(C++11 引入)
  • unique_ptr代表独占所有权,即:
    • 不支持拷贝,只允许移动std::move())。
    • 适用于不需要拷贝的场景,资源只能被一个对象管理。
    • 离开作用域时,自动释放资源

示例:

代码语言:javascript

AI代码解释

#include <iostream> #include <memory> class Test { public: void show() { std::cout << "使用 unique_ptr\n"; } }; int main() { std::unique_ptr<Test> ptr = std::make_unique<Test>(); ptr->show(); // std::unique_ptr<Test> ptr2 = ptr; // 错误!unique_ptr 不允许拷贝 std::unique_ptr<Test> ptr2 = std::move(ptr); // 允许移动 return 0; // 资源自动释放 }

3.shared_ptr(C++11 引入)
  • shared_ptr代表共享所有权,即:
    • 支持拷贝和移动,多个shared_ptr可以共享同一个资源
    • 底层使用引用计数来管理资源,当最后一个shared_ptr释放时,资源才被释放
    • 适用于需要多个对象共享同一资源的场景。

示例:

代码语言:javascript

AI代码解释

#include <iostream> #include <memory> class Test { public: void show() { std::cout << "使用 shared_ptr\n"; } }; int main() { std::shared_ptr<Test> p1 = std::make_shared<Test>(); // 推荐使用 make_shared std::shared_ptr<Test> p2 = p1; // p1 和 p2 共享同一资源 p1->show(); std::cout << "引用计数:" << p1.use_count() << std::endl; // 输出 2 return 0; // 只有当 p1 和 p2 都析构,资源才会释放 }

代码语言:javascript

AI代码解释

class Date { public: Date(int year=2025, int month=3, int day=15) :_year(year), _month(month), _day(day) { cout << "Date()" << endl; } ~Date() { cout << "~Date()" << endl; } int _year; int _month; int _day; }; #include <memory> int main() { //拷贝后,p1悬空了 /*auto_ptr<Date> p1(new Date(2018, 1, 1)); auto_ptr<Date> p2(p1); unique_ptr<Date> p3(new Date(2018, 1, 1));*/ //unique_ptr<Date> p4(p3)--error; //不支持拷贝,支持移动构造,移动后p3悬空 //unique_ptr<Date> p4(move(p3)); shared_ptr<Date> sp1(new Date); // ⽀持拷贝 shared_ptr<Date> sp2(sp1); shared_ptr<Date> sp3(sp2); cout << sp1.use_count() << endl; sp1->_year++; cout << sp1->_year << endl; cout << sp2->_year << endl; cout << sp3->_year << endl; // ⽀持移动,但是移动后sp1也悬空 shared_ptr<Date> sp4(move(sp1)); cout<<sp1.get() << endl; return 0; }

4.weak_ptr(C++11 引入)
  • weak_ptr代表弱引用,主要用于解决shared_ptr循环引用的问题。
  • weak_ptr不会影响引用计数,所以不能直接访问资源,必须通过lock()方法转换为shared_ptr才能使用。

示例:

代码语言:javascript

AI代码解释

#include <iostream> #include <memory> class Test { public: void show() { std::cout << "使用 weak_ptr\n"; } }; int main() { std::shared_ptr<Test> sp = std::make_shared<Test>(); std::weak_ptr<Test> wp = sp; // 不影响引用计数 if (auto ptr = wp.lock()) { // 需要转换为 shared_ptr 才能访问 ptr->show(); } else { std::cout << "对象已释放\n"; } return 0; }

5. 智能指针的删除器
  • 智能指针默认使用delete释放资源,因此不能直接用于管理非new分配的资源,否则会导致delete释放非法内存
  • unique_ptrshared_ptr支持自定义删除器,即在构造时传入一个可调用对象(函数、lambda、仿函数),来指定资源释放方式。
  • 例如new[]需要delete[]释放,因此unique_ptrshared_ptr特化了数组版本

代码语言:javascript

AI代码解释

//特化版本 unique_ptr<Date[]> p1(new Date[5]); shared_ptr<Date[]> p2(new Date[5]);

代码语言:javascript

AI代码解释

struct Date { int _year; int _month; int _day; Date(int year = 1, int month = 1, int day = 1) :_year(year) , _month(month) , _day(day) {} ~Date() { cout << "~Date()" << endl; } }; template<class T> class DeleteArray { public: void operator()(T* ptr) { delete[] ptr; } }; template<class T> void DeleteFunc(T* ptr) { delete[] ptr; } class Fclose { public: void operator()(FILE* ptr) { cout << "fclose:" << ptr << endl; fclose(ptr); } }; int main() { //程序崩溃 //unique_ptr<Date> up1(new Date[10]); //shared_ptr<Date> sp1(new Date[10]); // --------特化版本 //unique_ptr<Date[]> p1(new Date[5]); // shared_ptr<Date[]> p2(new Date[5]); //仿函数对象 /// unique_ptr<Date, DeleteArray<Date>> p3(new Date[10],DeleteArray<Date>()); // unique_ptr和shared_ptr支持删除器的方式有所不同 // unique_ptr是在类模板参数支持的,shared_ptr是构造函数参数支持的 // 使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调⽤ /*unique_ptr<Date, DeleteArray<Date>> p3(new Date[10]); shared_ptr<Date> p4(new Date[10], DeleteArray<Date>());*/ //函数指针版本 //unique_ptr<Date, void(*)(Date*)> p1(new Date[3], DeleteFunc<Date>); //shared_ptr<Date> p2(new Date[3], DeleteFunc<Date>); //lambda版本 auto Delete = [](Date* ptr) { delete[] ptr; }; //decltype(Delete) 获取 lambda 的类型,并作为 unique_ptr 的删除器类型。 unique_ptr<Date, decltype(Delete)> p1(new Date[3], Delete); shared_ptr<Date> p2(new Date[3], [](Date* ptr) { delete[] ptr; }); // 实现其他资源管理的删除器 shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose()); shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) { cout << "fclose:" << ptr << endl; fclose(ptr); }); return 0; }


6.make_shared(推荐使用)
  • shared_ptr除了可以直接用new资源构造,还可以使用make_shared(),这样更高效
    • make_shared()直接在shared_ptr的引用计数控制块中分配资源,减少一次new开销,提高性能。
    • make_shared()更安全,避免shared_ptr(new T())可能导致的内存泄漏(如果new T()之后shared_ptr构造失败,可能导致T资源泄漏)。

示例:

代码语言:javascript

AI代码解释

#include <iostream> #include <memory> int main() { std::shared_ptr<int> sp = std::make_shared<int>(42); // 直接构造对象 std::cout << "值:" << *sp << std::endl; return 0; }

7. 智能指针的bool转换

shared_ptrunique_ptr都支持operator bool,用于检查智能指针是否为空

代码语言:javascript

AI代码解释

std::unique_ptr<int> up; if (up) { /* 不为空 */ } else { /* 为空 */ }

这个特性可以直接用于if语句,简化空指针判断。


8. 防止隐式转换

shared_ptrunique_ptr的构造函数都使用explicit修饰,防止普通指针隐式转换为智能指针

代码语言:javascript

AI代码解释

void func(std::unique_ptr<int> ptr) {} int main() { // func(new int(10)); // ❌ 错误,不能隐式转换 func(std::make_unique<int>(10)); // ✅ 正确,必须显式转换 return 0; }


总结
  1. auto_ptr(已废弃):拷贝会导致原指针悬空,不安全。
  2. unique_ptr(推荐)独占所有权,不支持拷贝,支持移动,适用于不需要共享资源的情况。
  3. shared_ptr(推荐)共享所有权,支持拷贝和移动,底层使用引用计数
  4. weak_ptr弱引用,用于解决shared_ptr循环引用问题。
  5. 推荐使用make_shared(),更安全更高效
  6. 支持自定义删除器,适用于new[]和非new资源
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/4 6:38:41

Unity3D 语音操控效果演示

基于 Unity3D 引擎实现语音控制的模型动画切换系统。自动识别麦克风并解析语音指令&#xff0c;如跳跃、奔跑、换弹、射击、待机等&#xff0c;使 3D 模型实时切换对应动画。同时支持场景切换与程序退出等功能。 Unity3D 语音操控效果演示

作者头像 李华
网站建设 2026/3/4 23:17:50

Excalidraw离线使用指南:无网络环境下的应对策略

Excalidraw离线使用指南&#xff1a;无网络环境下的应对策略 在金融系统架构评审会上&#xff0c;投影仪突然断网&#xff0c;白板上的微服务拓扑图再无法同步更新&#xff1b;野外勘探队的工程师试图用AI生成井场布局草图&#xff0c;却因卫星信号中断而被迫中止——这些场景暴…

作者头像 李华
网站建设 2026/3/3 19:06:09

Excalidraw AI助手接入:自然语言驱动绘图实践

Excalidraw AI助手接入&#xff1a;自然语言驱动绘图实践 在技术团队的日常协作中&#xff0c;你是否经历过这样的场景&#xff1f;产品经理在会议中说&#xff1a;“我们来画个用户注册流程”&#xff0c;然后所有人盯着空白白板沉默三秒——有人开始手动拖拽矩形框&#xff0…

作者头像 李华
网站建设 2026/2/27 8:19:14

Excalidraw科研假设模型:理论框架可视化

Excalidraw科研假设模型&#xff1a;理论框架可视化 在一场跨学科的线上组会中&#xff0c;一位研究员突然停顿&#xff1a;“等等&#xff0c;你说的‘反馈回路’到底连接的是哪个模块&#xff1f;”——这样的场景在科研协作中并不陌生。当抽象概念仅靠语言传递时&#xff0c…

作者头像 李华
网站建设 2026/3/4 17:29:32

Excalidraw数据库ER图设计:后端开发提效利器

Excalidraw&#xff1a;用“手绘白板”重塑数据库设计流程 在一次紧急的需求评审会上&#xff0c;产品经理刚讲完新会员系统的业务逻辑&#xff0c;会议室里却陷入沉默——没人能立刻理清“用户、等级、权益、订阅”之间的数据关系。这时&#xff0c;有人打开了 Excalidraw&am…

作者头像 李华
网站建设 2026/2/26 19:30:14

Excalidraw家庭预算表:收支结构直观展示

Excalidraw家庭预算表&#xff1a;收支结构直观展示 在不少家庭的晚餐桌上&#xff0c;一个老生常谈的问题总是反复出现&#xff1a;“这个月钱又花到哪儿去了&#xff1f;”即使有记账习惯&#xff0c;面对密密麻麻的电子表格&#xff0c;大多数人依然难以快速抓住资金流向的核…

作者头像 李华