深入理解C++智能指针 - 新手必读指南
目录
- 1. 为什么要用智能指针?
- 2. unique_ptr详解
- 3. shared_ptr深度解析
- 4. weak_ptr的妙用
- 5. 新手常见错误大全
- 6. 智能指针使用黄金法则
1. 为什么要用智能指针?
现实场景:
假设你正在开发一个文档编辑器,需要频繁创建/销毁文本缓冲区:
void loadDocument() {
TextBuffer* buf = new TextBuffer();
// 如果这里出现异常...
delete buf; // 可能永远不会执行!
}
智能指针可以保证无论是否发生异常,内存都会被正确释放
2. unique_ptr详解
工作原理:
就像你的个人保险箱:
- 只有一把钥匙(所有权)
- 钥匙可以转交他人(移动语义)
- 离开作用域自动销毁
完整使用示例:
#include <memory>
#include <vector>
// 创建独占指针
std::unique_ptr<std::vector<int>> createVector() {
auto ptr = std::make_unique<std::vector<int>>();
ptr->push_back(42);
return ptr; // 通过移动操作返回
}
int main() {
std::unique_ptr<std::vector<int>> data;
{
auto temp = createVector();
data = std::move(temp); // 所有权转移
// temp现在为空指针
}
std::cout << data->at(0); // 安全访问
return 0;
}
典型错误:
auto ptr1 = std::make_unique<int>(10);
auto ptr2 = ptr1; // 错误!尝试复制
// 正确做法:
auto ptr2 = std::move(ptr1); // 显式转移所有权
编译器会直接报错,避免潜在的内存问题
3. shared_ptr深度解析
核心机制:
想象办公室的公共打印机:
- 每个使用者登记(引用计数+1)
- 最后一个离开的人关打印机(计数归零时释放)
- 控制块存储计数信息
完整生命周期示例:
class NetworkConnection {
public:
NetworkConnection() { std::cout << "连接建立\n"; }
~NetworkConnection() { std::cout << "连接关闭\n"; }
};
void processData() {
auto conn = std::make_shared<NetworkConnection>();
// 引用计数:1
{
auto conn2 = conn; // 复制指针
// 引用计数:2
conn->send(data);
} // conn2析构,计数回到1
conn.reset(); // 手动释放,计数归零
// 输出"连接关闭"
}
循环引用陷阱:
class ChatUser {
public:
std::shared_ptr<ChatUser> friend;
};
auto alice = std::make_shared<ChatUser>();
auto bob = std::make_shared<ChatUser>();
alice->friend = bob;
bob->friend = alice; // 循环引用!
即使alice和bob离开作用域,引用计数也不会归零
4. weak_ptr的妙用
使用场景:
就像查看餐厅排队情况:
- 可以查看是否有空位(lock()检查)
- 不会影响餐厅关闭时间(不增加引用计数)
完整缓存示例:
class ResourceCache {
std::weak_ptr<Texture> cachedTexture;
public:
std::shared_ptr<Texture> getTexture() {
if(auto tex = cachedTexture.lock()) {
return tex; // 缓存命中
} else {
auto newTex = loadTexture();
cachedTexture = newTex;
return newTex; // 重新加载
}
}
};
void renderScene() {
auto cache = std::make_shared<ResourceCache>();
auto texture = cache->getTexture();
// 使用texture...
} // texture释放时,缓存自动失效
5. 新手常见错误大全
错误1:跨DLL内存管理
// 在DLL A中创建
std::shared_ptr<Data> createData() {
return std::make_shared<Data>();
}
// 在DLL B中释放
void processData() {
auto data = createData();
} // 可能导致崩溃!
解决方案:确保分配/释放在同一模块
错误2:错误使用原始指针
auto sp = std::make_shared<int>(10);
int* raw = sp.get();
{
auto sp2 = std::shared_ptr<int>(raw);
} // 此处释放内存!
*sp = 20; // 悬空指针!
6. 智能指针使用黄金法则
场景 | 推荐方案 | 示例 |
独占资源 | unique_ptr | 文件句柄、数据库连接 |
共享资源 | shared_ptr | 缓存数据、UI组件 |
观察资源 | weak_ptr | 缓存系统、解耦模块 |
性能优化建议:
- 优先使用make_shared/make_unique
- 避免大对象的shared_ptr拷贝
- 高频访问数据使用unique_ptr