提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 一、`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_ptr,weak_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[],而裸指针容易因delete和delete[]混淆导致内存泄漏或崩溃。
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;}四、使用智能指针的注意事项
不要管理栈上的对象:智能指针的析构函数会调用
delete,若指向栈上对象,会导致double free(栈对象由系统自动释放)。Product obj;// 错误:指向栈对象,析构时会delete &obj,导致崩溃// std::unique_ptr<Product> p(&obj);不要将同一个裸指针交给多个智能指针管理:会导致多个智能指针各自释放同一个对象,引发
double free。Product*raw=newProduct();// 错误:p1和p2都管理raw,析构时两次delete raw// std::unique_ptr<Product> p1(raw);// std::unique_ptr<Product> p2(raw);避免
shared_ptr的循环引用:一旦出现循环引用,必须用weak_ptr打破。shared_ptr的线程安全:shared_ptr的引用计数操作是线程安全的,但对象的访问不是,多线程访问对象时需加锁同步。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[])的场景,都应优先使用智能指针替代裸指针,以提升代码的安全性和可维护性。