提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
-
-
- 一、`unique_ptr`:独占所有权(推荐优先使用)
-
- [1. 函数返回动态分配的对象(替代裸指针)](#1. 函数返回动态分配的对象(替代裸指针))
- [2. 管理类的独占成员资源](#2. 管理类的独占成员资源)
- [3. 存储多态对象的容器(多态场景首选)](#3. 存储多态对象的容器(多态场景首选))
- [4. 管理动态数组(避免`delete`/`delete[]`混淆)](#4. 管理动态数组(避免
delete/delete[]混淆)) - [5. 局部动态对象的异常安全](#5. 局部动态对象的异常安全)
- 二、`shared_ptr`:共享所有权(引用计数)
-
- [1. 多个对象共享同一个资源](#1. 多个对象共享同一个资源)
- [2. 跨作用域共享对象](#2. 跨作用域共享对象)
- [3. 管理非内存资源(自定义删除器)](#3. 管理非内存资源(自定义删除器))
- 注意:优先使用`make_shared`创建`shared_ptr`
- 三、`weak_ptr`:弱引用(配合`shared_ptr`)
-
- [1. 解决`shared_ptr`的循环引用(核心场景)](#1. 解决
shared_ptr的循环引用(核心场景)) - [2. 观察共享资源的存在性(不影响资源释放)](#2. 观察共享资源的存在性(不影响资源释放))
- [3. 回调函数中避免`this`指针的循环引用](#3. 回调函数中避免
this指针的循环引用)
- [1. 解决`shared_ptr`的循环引用(核心场景)](#1. 解决
- 四、使用智能指针的注意事项
- 总结
-
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可自动释放。
cpp
#include <memory>
#include <iostream>
class Product {
public:
~Product() { std::cout << "Product destroyed\n"; }
};
// 工厂函数:返回独占的动态对象
std::unique_ptr<Product> createProduct() {
// 直接返回,编译器会自动优化移动操作
return std::unique_ptr<Product>(new Product());
// C++14可使用make_unique,更安全
// return std::make_unique<Product>();
}
int main() {
auto product = createProduct(); // 接收对象,自动管理生命周期
return 0; // 函数结束,product析构,释放Product
}
2. 管理类的独占成员资源
场景:类的成员变量是动态分配的资源(如网络连接、文件句柄、自定义资源),使用unique_ptr作为成员,析构时自动释放,无需手动写析构函数。
cpp
class Resource {
public:
~Resource() { std::cout << "Resource destroyed\n"; }
};
class MyClass {
private:
// 独占Resource,MyClass析构时自动释放
std::unique_ptr<Resource> res;
public:
MyClass() : res(std::make_unique<Resource>()) {}
};
int main() {
MyClass obj; // 析构时,obj的res成员自动释放Resource
return 0;
}
3. 存储多态对象的容器(多态场景首选)
场景:需要存储基类指针指向派生类对象的容器(如vector<Base*>),使用unique_ptr既支持多态,又能自动管理生命周期,且开销远小于shared_ptr。
cpp
#include <vector>
class Base {
public:
virtual ~Base() { std::cout << "Base destroyed\n"; }
};
class Derived : public Base {
public:
~Derived() override { std::cout << "Derived destroyed\n"; }
};
int main() {
// 存储多态对象的容器,独占所有权
std::vector<std::unique_ptr<Base>> polyVec;
polyVec.push_back(std::make_unique<Derived>());
polyVec.push_back(std::make_unique<Derived>());
// 容器析构时,所有元素自动释放,调用派生类析构
return 0;
}
4. 管理动态数组(避免delete/delete[]混淆)
场景:动态数组的管理,unique_ptr<T[]>专门支持数组,自动调用delete[],而裸指针容易因delete和delete[]混淆导致内存泄漏或崩溃。
cpp
int main() {
// 管理动态数组,析构时调用delete[]
std::unique_ptr<int[]> arr(new int[10]{1,2,3});
arr[0] = 10; // 支持[]运算符
return 0; // 自动释放数组
}
5. 局部动态对象的异常安全
场景:局部动态对象创建后,若中间代码抛出异常,裸指针的delete无法执行导致内存泄漏;unique_ptr在析构时(即使异常)会自动释放资源。
cpp
void riskyFunc() {
// 裸指针:若下面抛出异常,obj无法被delete
// Product* obj = new Product();
// 智能指针:即使抛异常,obj析构时释放
std::unique_ptr<Product> obj = std::make_unique<Product>();
// 模拟抛出异常
throw std::runtime_error("something wrong");
}
int main() {
try {
riskyFunc();
} catch (const std::exception& e) {
std::cout << e.what() << '\n';
}
return 0;
}
二、shared_ptr:共享所有权(引用计数)
shared_ptr通过引用计数 实现共享所有权,多个shared_ptr可指向同一个对象,最后一个shared_ptr销毁时,对象才会被释放。其有额外开销(存储引用计数的控制块),支持拷贝,适用场景如下:
1. 多个对象共享同一个资源
场景:多个类实例、模块需要共享同一个资源(如缓存、数据源、配置对象),使用shared_ptr保证资源在最后一个使用者销毁时释放。
cpp
class Cache {
public:
~Cache() { std::cout << "Cache destroyed\n"; }
void read() { std::cout << "Cache read\n"; }
};
// 缓存使用者:共享同一个Cache
class CacheUser {
private:
std::shared_ptr<Cache> cache;
public:
CacheUser(std::shared_ptr<Cache> c) : cache(c) {}
void useCache() { cache->read(); }
};
int main() {
auto cache = std::make_shared<Cache>(); // 引用计数=1
CacheUser user1(cache); // 引用计数=2
CacheUser user2(cache); // 引用计数=3
user1.useCache();
// user1、user2、cache依次销毁,引用计数减至0,Cache释放
return 0;
}
2. 跨作用域共享对象
场景:对象需要在多个函数、作用域间传递,且每个作用域都需要访问该对象,shared_ptr可保证对象在所有使用者都结束后释放。
cpp
void processObj(std::shared_ptr<Product> p) {
// 引用计数+1
std::cout << "Processing obj, ref count: " << p.use_count() << '\n';
}
int main() {
auto p = std::make_shared<Product>(); // 引用计数=1
std::cout << "Before process, ref count: " << p.use_count() << '\n';
processObj(p); // 传递后引用计数=2,函数结束后减为1
return 0; // p销毁,引用计数=0,Product释放
}
3. 管理非内存资源(自定义删除器)
场景:除了动态内存,还可管理文件句柄、socket、数据库连接等资源,通过自定义删除器 替代delete,实现资源的自动释放。
cpp
#include <cstdio>
// 管理文件句柄,自定义删除器为fclose
int main() {
std::shared_ptr<FILE> fp(fopen("test.txt", "r"), [](FILE* f) {
if (f) fclose(f);
std::cout << "File closed\n";
});
if (fp) {
// 操作文件
}
return 0; // 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即可打破循环。
cpp
class Parent;
class Child;
class Parent {
public:
// 持有子对象的共享指针
std::shared_ptr<Child> child;
~Parent() { std::cout << "Parent destroyed\n"; }
};
class Child {
public:
// 改为弱引用,不增加Parent的引用计数
std::weak_ptr<Parent> parent;
~Child() { std::cout << "Child destroyed\n"; }
// 访问父对象(需要先lock())
void accessParent() {
if (auto p = parent.lock()) { // lock()返回shared_ptr,若对象存在则有效
std::cout << "Parent exists\n";
} else {
std::cout << "Parent destroyed\n";
}
}
};
int main() {
auto parent = std::make_shared<Parent>();
auto child = std::make_shared<Child>();
parent->child = child; // Parent的引用计数=1,Child的引用计数=2
child->parent = parent; // Parent的引用计数仍为1(weak_ptr不增加)
child->accessParent();
// parent、child销毁:Child的引用计数减为1→0,释放Child;Parent的引用计数减为0,释放Parent
return 0;
}
2. 观察共享资源的存在性(不影响资源释放)
场景:缓存系统中,缓存对象用shared_ptr管理,观察者用weak_ptr指向缓存对象,需要时通过lock()获取shared_ptr(若缓存未释放则可用,否则返回空),避免观察者持有资源导致其无法被释放。
cpp
// 缓存管理器
class CacheManager {
private:
std::shared_ptr<Cache> cache;
public:
void setCache(std::shared_ptr<Cache> c) { cache = c; }
// 返回弱引用,观察者不影响缓存的释放
std::weak_ptr<Cache> getCacheWeak() { return cache; }
};
int main() {
CacheManager manager;
manager.setCache(std::make_shared<Cache>());
// 观察者获取弱引用
auto cacheWeak = manager.getCacheWeak();
// 检查缓存是否存在
if (auto cache = cacheWeak.lock()) {
cache->read();
} else {
std::cout << "Cache not exists\n";
}
// 清空缓存
manager.setCache(nullptr);
if (auto cache = cacheWeak.lock()) {
cache->read();
} else {
std::cout << "Cache not exists\n";
}
return 0;
}
3. 回调函数中避免this指针的循环引用
场景:类的成员函数作为回调函数时,若回调管理器持有shared_ptr<Class>,而类又持有回调管理器的shared_ptr,会导致循环引用。此时用weak_ptr包裹this(需类继承std::enable_shared_from_this),避免循环。
cpp
#include <functional>
#include <vector>
class CallbackManager {
private:
std::vector<std::function<void()>> callbacks;
public:
void addCallback(std::function<void()> cb) {
callbacks.push_back(cb);
}
void runCallbacks() {
for (auto& cb : callbacks) {
cb();
}
}
};
// 继承enable_shared_from_this,才能获取this的shared_ptr
class MyClass : public std::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"; }
void registerCallback() {
// 用weak_ptr包裹this,避免循环引用
std::weak_ptr<MyClass> self = shared_from_this();
manager->addCallback([self]() {
// lock()检查对象是否存在
if (auto p = self.lock()) {
p->callbackFunc();
}
});
}
void callbackFunc() {
std::cout << "Callback executed\n";
}
};
int main() {
auto manager = std::make_shared<CallbackManager>();
auto obj = std::make_shared<MyClass>(manager);
manager->runCallbacks();
return 0;
}
四、使用智能指针的注意事项
-
不要管理栈上的对象 :智能指针的析构函数会调用
delete,若指向栈上对象,会导致double free(栈对象由系统自动释放)。cppProduct obj; // 错误:指向栈对象,析构时会delete &obj,导致崩溃 // std::unique_ptr<Product> p(&obj); -
不要将同一个裸指针交给多个智能指针管理 :会导致多个智能指针各自释放同一个对象,引发
double free。cppProduct* raw = new Product(); // 错误: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[]>更方便)。cpp// 正确:shared_ptr管理数组,自定义删除器 std::shared_ptr<int> arr(new int[10], [](int* p) { delete[] p; });
总结
智能指针的使用需遵循所有权语义:
- 独占所有权 :优先使用
unique_ptr(轻量级、高效); - 共享所有权 :使用
shared_ptr(引用计数,有开销); - 观察共享资源/解决循环引用 :使用
weak_ptr(配合shared_ptr)。
在所有需要动态分配内存(new/new[])的场景,都应优先使用智能指针替代裸指针,以提升代码的安全性和可维护性。