C++ shared ptr 详解
C++ shared_ptr 是 C++11 标准引入的智能指针之一,用于管理动态分配的内存。它通过引用计数机制实现资源的自动释放,有效避免了内存泄漏问题。本文将从基础概念到高级用法,全面解析 `shared_ptr` 的工作原理和使用方法。
概述[编辑 | 编辑源代码]
`shared_ptr` 是一种共享所有权的智能指针,多个 `shared_ptr` 可以指向同一个对象,并通过内部的引用计数器跟踪对象的引用次数。当最后一个 `shared_ptr` 被销毁时,对象会被自动删除。
核心特性[编辑 | 编辑源代码]
- 引用计数:记录当前有多少个 `shared_ptr` 共享同一对象
- 线程安全:引用计数的增减操作是原子性的(但对象访问仍需额外同步)
- 自定义删除器:支持指定自定义的删除逻辑
- 弱引用支持:可与 `weak_ptr` 配合使用,解决循环引用问题
基本用法[编辑 | 编辑源代码]
[编辑 | 编辑源代码]
#include <memory>
#include <iostream>
int main() {
// 方式1:使用make_shared(推荐)
std::shared_ptr<int> p1 = std::make_shared<int>(42);
// 方式2:直接构造
std::shared_ptr<int> p2(new int(100));
// 方式3:通过reset
std::shared_ptr<int> p3;
p3.reset(new int(200));
std::cout << *p1 << " " << *p2 << " " << *p3 << std::endl;
return 0;
}
输出:
42 100 200
引用计数机制[编辑 | 编辑源代码]
可以通过 `use_count()` 方法查看当前引用计数:
#include <memory>
#include <iostream>
void showCount(std::shared_ptr<int> p) {
std::cout << "count: " << p.use_count() << std::endl;
}
int main() {
auto p = std::make_shared<int>(10);
std::cout << "count after creation: " << p.use_count() << std::endl; // 1
{
auto p2 = p;
std::cout << "count after copy: " << p.use_count() << std::endl; // 2
showCount(p); // 函数内会再创建一个拷贝,count: 3
}
std::cout << "count after scope: " << p.use_count() << std::endl; // 1
return 0;
}
输出:
count after creation: 1 count after copy: 2 count: 3 count after scope: 1
高级特性[编辑 | 编辑源代码]
自定义删除器[编辑 | 编辑源代码]
`shared_ptr` 允许指定自定义的删除逻辑:
#include <memory>
#include <iostream>
void customDeleter(int* p) {
std::cout << "Custom deleter called for " << *p << std::endl;
delete p;
}
int main() {
std::shared_ptr<int> p(new int(99), customDeleter);
// 当p离开作用域时,customDeleter会被调用
return 0;
}
输出:
Custom deleter called for 99
数组支持[编辑 | 编辑源代码]
C++17 起支持数组类型的 `shared_ptr`:
// C++17及以上版本
std::shared_ptr<int[]> arr(new int[10]);
arr[0] = 42; // 可以直接使用下标操作
循环引用问题[编辑 | 编辑源代码]
`shared_ptr` 可能导致循环引用,这时需要使用 `weak_ptr` 来打破循环:
示例解决方案:
#include <memory>
#include <iostream>
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr打破循环
~Node() { std::cout << "Node destroyed\n"; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1; // 使用weak_ptr而非shared_ptr
// 当离开作用域时,node1和node2都能正确释放
return 0;
}
性能考虑[编辑 | 编辑源代码]
- make_shared 的优势:单次内存分配(对象和控制块),比直接 `new` 更高效
- 原子操作开销:引用计数的修改需要原子操作,有一定性能开销
- 内存占用:每个 `shared_ptr` 需要存储指向控制块的指针
实际应用案例[编辑 | 编辑源代码]
资源管理[编辑 | 编辑源代码]
管理数据库连接等需要自动释放的资源:
class DatabaseConnection {
public:
static std::shared_ptr<DatabaseConnection> create() {
return std::shared_ptr<DatabaseConnection>(new DatabaseConnection(),
[](DatabaseConnection* conn) {
conn->close();
delete conn;
});
}
void query(const std::string& sql) { /* ... */ }
void close() { /* ... */ }
private:
DatabaseConnection() { /* 建立连接 */ }
};
void processData() {
auto db = DatabaseConnection::create();
db->query("SELECT * FROM users");
// 不需要手动关闭连接,shared_ptr会确保资源释放
}
与裸指针的交互[编辑 | 编辑源代码]
获取原始指针[编辑 | 编辑源代码]
使用 `get()` 方法,但要注意生命周期管理:
void legacyFunction(int* p) { /* ... */ }
int main() {
auto ptr = std::make_shared<int>(42);
legacyFunction(ptr.get()); // 安全,只要ptr存在
// 危险示例
int* dangerous = nullptr;
{
auto temp = std::make_shared<int>(100);
dangerous = temp.get();
} // temp被销毁
// dangerous现在悬垂指针!
return 0;
}
最佳实践[编辑 | 编辑源代码]
1. 优先使用 `make_shared` 而非直接 `new` 2. 避免将 `shared_ptr` 的原始指针暴露给外部 3. 对于可能产生循环引用的场景使用 `weak_ptr` 4. 在多线程环境中,仍需对共享对象本身进行同步保护 5. 不要将 `this` 指针直接转换为 `shared_ptr`(使用 `enable_shared_from_this`)
数学表示[编辑 | 编辑源代码]
引用计数机制可以表示为: 当 时,对象 被销毁。
总结[编辑 | 编辑源代码]
`shared_ptr` 是 C++ 中强大的资源管理工具,通过引用计数自动管理对象生命周期。正确使用它可以显著减少内存泄漏问题,但需要注意循环引用和线程安全等潜在问题。结合 `make_shared`、`weak_ptr` 等配套工具,可以构建出既安全又高效的资源管理系统。