news 2026/4/2 21:04:41

智能指针使用场景

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
智能指针使用场景

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

      • 一、`unique_ptr`:独占所有权(推荐优先使用)
        • 1. 函数返回动态分配的对象(替代裸指针)
        • 2. 管理类的独占成员资源
        • 3. 存储多态对象的容器(多态场景首选)
        • 4. 管理动态数组(避免`delete`/`delete[]`混淆)
        • 5. 局部动态对象的异常安全
      • 二、`shared_ptr`:共享所有权(引用计数)
        • 1. 多个对象共享同一个资源
        • 2. 跨作用域共享对象
        • 3. 管理非内存资源(自定义删除器)
        • 注意:优先使用`make_shared`创建`shared_ptr`
      • 三、`weak_ptr`:弱引用(配合`shared_ptr`)
        • 1. 解决`shared_ptr`的循环引用(核心场景)
        • 2. 观察共享资源的存在性(不影响资源释放)
        • 3. 回调函数中避免`this`指针的循环引用
      • 四、使用智能指针的注意事项
      • 总结

C++智能指针的核心是RAII(资源获取即初始化)机制,它能自动管理动态内存的生命周期,解决裸指针带来的内存泄漏、野指针、double free、异常安全等问题。C++11及以上版本中,主流的智能指针有unique_ptr(独占所有权)、shared_ptr(共享所有权)、weak_ptr(弱引用,配合shared_ptr使用),废弃了老旧的auto_ptr

选择智能指针的核心原则是:根据所有权语义选择(独占/共享),优先使用轻量级的unique_ptr,仅在需要共享时使用shared_ptrweak_ptr仅作为辅助解决循环引用或观察资源。

下面分场景详细说明各智能指针的使用场景:

一、unique_ptr:独占所有权(推荐优先使用)

unique_ptr轻量级智能指针(无额外内存开销,仅封装裸指针),具有移动语义(可通过std::move转移所有权),但不可拷贝,代表对资源的独占所有权。其适用场景如下:

1. 函数返回动态分配的对象(替代裸指针)

场景:工厂模式、创建动态对象的函数,返回裸指针时调用者容易忘记delete导致内存泄漏,而unique_ptr可自动释放。

#include<memory>#include<iostream>classProduct{public:~Product(){std::cout<<"Product destroyed\n";}};// 工厂函数:返回独占的动态对象std::unique_ptr<Product>createProduct(){// 直接返回,编译器会自动优化移动操作returnstd::unique_ptr<Product>(newProduct());// C++14可使用make_unique,更安全// return std::make_unique<Product>();}intmain(){autoproduct=createProduct();// 接收对象,自动管理生命周期return0;// 函数结束,product析构,释放Product}
2. 管理类的独占成员资源

场景:类的成员变量是动态分配的资源(如网络连接、文件句柄、自定义资源),使用unique_ptr作为成员,析构时自动释放,无需手动写析构函数。

classResource{public:~Resource(){std::cout<<"Resource destroyed\n";}};classMyClass{private:// 独占Resource,MyClass析构时自动释放std::unique_ptr<Resource>res;public:MyClass():res(std::make_unique<Resource>()){}};intmain(){MyClass obj;// 析构时,obj的res成员自动释放Resourcereturn0;}
3. 存储多态对象的容器(多态场景首选)

场景:需要存储基类指针指向派生类对象的容器(如vector<Base*>),使用unique_ptr既支持多态,又能自动管理生命周期,且开销远小于shared_ptr

#include<vector>classBase{public:virtual~Base(){std::cout<<"Base destroyed\n";}};classDerived:publicBase{public:~Derived()override{std::cout<<"Derived destroyed\n";}};intmain(){// 存储多态对象的容器,独占所有权std::vector<std::unique_ptr<Base>>polyVec;polyVec.push_back(std::make_unique<Derived>());polyVec.push_back(std::make_unique<Derived>());// 容器析构时,所有元素自动释放,调用派生类析构return0;}
4. 管理动态数组(避免delete/delete[]混淆)

场景:动态数组的管理,unique_ptr<T[]>专门支持数组,自动调用delete[],而裸指针容易因deletedelete[]混淆导致内存泄漏或崩溃。

intmain(){// 管理动态数组,析构时调用delete[]std::unique_ptr<int[]>arr(newint[10]{1,2,3});arr[0]=10;// 支持[]运算符return0;// 自动释放数组}
5. 局部动态对象的异常安全

场景:局部动态对象创建后,若中间代码抛出异常,裸指针的delete无法执行导致内存泄漏;unique_ptr在析构时(即使异常)会自动释放资源。

voidriskyFunc(){// 裸指针:若下面抛出异常,obj无法被delete// Product* obj = new Product();// 智能指针:即使抛异常,obj析构时释放std::unique_ptr<Product>obj=std::make_unique<Product>();// 模拟抛出异常throwstd::runtime_error("something wrong");}intmain(){try{riskyFunc();}catch(conststd::exception&e){std::cout<<e.what()<<'\n';}return0;}

二、shared_ptr:共享所有权(引用计数)

shared_ptr通过引用计数实现共享所有权,多个shared_ptr可指向同一个对象,最后一个shared_ptr销毁时,对象才会被释放。其有额外开销(存储引用计数的控制块),支持拷贝,适用场景如下:

1. 多个对象共享同一个资源

场景:多个类实例、模块需要共享同一个资源(如缓存、数据源、配置对象),使用shared_ptr保证资源在最后一个使用者销毁时释放。

classCache{public:~Cache(){std::cout<<"Cache destroyed\n";}voidread(){std::cout<<"Cache read\n";}};// 缓存使用者:共享同一个CacheclassCacheUser{private:std::shared_ptr<Cache>cache;public:CacheUser(std::shared_ptr<Cache>c):cache(c){}voiduseCache(){cache->read();}};intmain(){autocache=std::make_shared<Cache>();// 引用计数=1CacheUseruser1(cache);// 引用计数=2CacheUseruser2(cache);// 引用计数=3user1.useCache();// user1、user2、cache依次销毁,引用计数减至0,Cache释放return0;}
2. 跨作用域共享对象

场景:对象需要在多个函数、作用域间传递,且每个作用域都需要访问该对象,shared_ptr可保证对象在所有使用者都结束后释放。

voidprocessObj(std::shared_ptr<Product>p){// 引用计数+1std::cout<<"Processing obj, ref count: "<<p.use_count()<<'\n';}intmain(){autop=std::make_shared<Product>();// 引用计数=1std::cout<<"Before process, ref count: "<<p.use_count()<<'\n';processObj(p);// 传递后引用计数=2,函数结束后减为1return0;// p销毁,引用计数=0,Product释放}
3. 管理非内存资源(自定义删除器)

场景:除了动态内存,还可管理文件句柄、socket、数据库连接等资源,通过自定义删除器替代delete,实现资源的自动释放。

#include<cstdio>// 管理文件句柄,自定义删除器为fcloseintmain(){std::shared_ptr<FILE>fp(fopen("test.txt","r"),[](FILE*f){if(f)fclose(f);std::cout<<"File closed\n";});if(fp){// 操作文件}return0;// fp析构,调用自定义删除器关闭文件}
注意:优先使用make_shared创建shared_ptr

make_shared比直接new更高效(一次分配对象和控制块的内存,而shared_ptr(new T)需要两次分配),且更安全(避免异常导致的内存泄漏)。

三、weak_ptr:弱引用(配合shared_ptr

weak_ptr弱引用,不拥有资源,也不增加shared_ptr的引用计数,仅用于观察shared_ptr管理的资源。其核心作用是解决shared_ptr循环引用问题,也可用于观察资源是否存在。

1. 解决shared_ptr的循环引用(核心场景)

场景:两个对象互相持有shared_ptr,导致引用计数无法归零,内存泄漏。将其中一方改为weak_ptr即可打破循环。

classParent;classChild;classParent{public:// 持有子对象的共享指针std::shared_ptr<Child>child;~Parent(){std::cout<<"Parent destroyed\n";}};classChild{public:// 改为弱引用,不增加Parent的引用计数std::weak_ptr<Parent>parent;~Child(){std::cout<<"Child destroyed\n";}// 访问父对象(需要先lock())voidaccessParent(){if(autop=parent.lock()){// lock()返回shared_ptr,若对象存在则有效std::cout<<"Parent exists\n";}else{std::cout<<"Parent destroyed\n";}}};intmain(){autoparent=std::make_shared<Parent>();autochild=std::make_shared<Child>();parent->child=child;// Parent的引用计数=1,Child的引用计数=2child->parent=parent;// Parent的引用计数仍为1(weak_ptr不增加)child->accessParent();// parent、child销毁:Child的引用计数减为1→0,释放Child;Parent的引用计数减为0,释放Parentreturn0;}
2. 观察共享资源的存在性(不影响资源释放)

场景:缓存系统中,缓存对象用shared_ptr管理,观察者用weak_ptr指向缓存对象,需要时通过lock()获取shared_ptr(若缓存未释放则可用,否则返回空),避免观察者持有资源导致其无法被释放。

// 缓存管理器classCacheManager{private:std::shared_ptr<Cache>cache;public:voidsetCache(std::shared_ptr<Cache>c){cache=c;}// 返回弱引用,观察者不影响缓存的释放std::weak_ptr<Cache>getCacheWeak(){returncache;}};intmain(){CacheManager manager;manager.setCache(std::make_shared<Cache>());// 观察者获取弱引用autocacheWeak=manager.getCacheWeak();// 检查缓存是否存在if(autocache=cacheWeak.lock()){cache->read();}else{std::cout<<"Cache not exists\n";}// 清空缓存manager.setCache(nullptr);if(autocache=cacheWeak.lock()){cache->read();}else{std::cout<<"Cache not exists\n";}return0;}
3. 回调函数中避免this指针的循环引用

场景:类的成员函数作为回调函数时,若回调管理器持有shared_ptr<Class>,而类又持有回调管理器的shared_ptr,会导致循环引用。此时用weak_ptr包裹this(需类继承std::enable_shared_from_this),避免循环。

#include<functional>#include<vector>classCallbackManager{private:std::vector<std::function<void()>>callbacks;public:voidaddCallback(std::function<void()>cb){callbacks.push_back(cb);}voidrunCallbacks(){for(auto&cb:callbacks){cb();}}};// 继承enable_shared_from_this,才能获取this的shared_ptrclassMyClass:publicstd::enable_shared_from_this<MyClass>{private:std::shared_ptr<CallbackManager>manager;public:MyClass(std::shared_ptr<CallbackManager>m):manager(m){registerCallback();}~MyClass(){std::cout<<"MyClass destroyed\n";}voidregisterCallback(){// 用weak_ptr包裹this,避免循环引用std::weak_ptr<MyClass>self=shared_from_this();manager->addCallback([self](){// lock()检查对象是否存在if(autop=self.lock()){p->callbackFunc();}});}voidcallbackFunc(){std::cout<<"Callback executed\n";}};intmain(){automanager=std::make_shared<CallbackManager>();autoobj=std::make_shared<MyClass>(manager);manager->runCallbacks();return0;}

四、使用智能指针的注意事项

  1. 不要管理栈上的对象:智能指针的析构函数会调用delete,若指向栈上对象,会导致double free(栈对象由系统自动释放)。

    Product obj;// 错误:指向栈对象,析构时会delete &obj,导致崩溃// std::unique_ptr<Product> p(&obj);
  2. 不要将同一个裸指针交给多个智能指针管理:会导致多个智能指针各自释放同一个对象,引发double free

    Product*raw=newProduct();// 错误:p1和p2都管理raw,析构时两次delete raw// std::unique_ptr<Product> p1(raw);// std::unique_ptr<Product> p2(raw);
  3. 避免shared_ptr的循环引用:一旦出现循环引用,必须用weak_ptr打破。

  4. shared_ptr的线程安全shared_ptr引用计数操作是线程安全的,但对象的访问不是,多线程访问对象时需加锁同步。

  5. shared_ptr管理动态数组需自定义删除器shared_ptr默认调用delete,管理数组时需手动指定删除器(unique_ptr<T[]>更方便)。

    // 正确:shared_ptr管理数组,自定义删除器std::shared_ptr<int>arr(newint[10],[](int*p){delete[]p;});

总结

智能指针的使用需遵循所有权语义

  • 独占所有权:优先使用unique_ptr(轻量级、高效);
  • 共享所有权:使用shared_ptr(引用计数,有开销);
  • 观察共享资源/解决循环引用:使用weak_ptr(配合shared_ptr)。

在所有需要动态分配内存(new/new[])的场景,都应优先使用智能指针替代裸指针,以提升代码的安全性和可维护性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/31 6:50:22

Chatbot Arena排行榜背后的技术原理与实现解析

Chatbot Arena 排行榜背后的技术原理与实现解析 背景与痛点&#xff1a;为什么“谁更聪明”这么难量化 1.1 成本爆炸&#xff1a;一次 1000 轮盲测&#xff0c;GPT-4 级模型仅推理就要烧掉上千美元 GPU 时&#xff0c;若再引入人工标注&#xff0c;预算直接翻倍。 1.2 主观偏差…

作者头像 李华
网站建设 2026/3/31 16:16:54

CogVideoX-2b应用案例:自媒体短视频高效制作方案

CogVideoX-2b应用案例&#xff1a;自媒体短视频高效制作方案 1. 为什么自媒体人需要本地化视频生成工具&#xff1f; 你是不是也经历过这些场景&#xff1a; 想发一条产品介绍短视频&#xff0c;却卡在“找剪辑师”“等成片”“反复修改”上&#xff0c;三天还没发出去&…

作者头像 李华
网站建设 2026/4/1 5:22:21

ChatTTS电脑版实战:如何构建高并发的语音合成服务

背景痛点&#xff1a;PC端语音合成服务的三座大山 把 ChatTTS 搬到 Windows 工作站后&#xff0c;最先撞上的不是算法精度&#xff0c;而是“PC 级”部署独有的三件套&#xff1a; 线程阻塞&#xff1a;默认的 torch.nn.Module.forward() 会霸占 Python GIL&#xff0c;10 路…

作者头像 李华
网站建设 2026/3/29 2:20:30

PyTorch通用环境使用避坑指南,新手少走弯路

PyTorch通用环境使用避坑指南&#xff0c;新手少走弯路 1. 为什么需要这份避坑指南&#xff1f; 刚接触深度学习开发的新手&#xff0c;常常在环境配置上耗费数小时甚至一整天——明明只是想跑通一个简单的训练脚本&#xff0c;却卡在torch.cuda.is_available()返回False、Im…

作者头像 李华
网站建设 2026/3/27 7:39:14

语音助手新玩法:用SenseVoiceSmall增加情绪感知能力

语音助手新玩法&#xff1a;用SenseVoiceSmall增加情绪感知能力 你有没有遇到过这样的场景&#xff1a; 语音助手准确听懂了你说的每个字&#xff0c;却完全没察觉你正焦躁地敲着桌子、语气里带着不耐烦&#xff1f; 或者会议录音转成文字后&#xff0c;所有发言都平铺直叙&am…

作者头像 李华