题目
智能指针的所有权转移与自定义删除器
信息
- 类型:问答
- 难度:⭐⭐
考点
智能指针的所有权语义,自定义删除器的使用,资源管理
快速回答
关键要点:
std::unique_ptr通过移动语义实现独占所有权的转移- 自定义删除器用于管理非标准资源(如文件句柄、网络连接)
- 删除器类型影响智能指针的拷贝语义和大小
- 推荐使用
std::move显式转移所有权 - 避免在删除器中抛出异常(可能导致未定义行为)
1. 所有权转移原理
std::unique_ptr 采用独占所有权模型:
- 通过禁用拷贝构造函数/赋值运算符保证独占性
- 移动操作(移动构造/移动赋值)转移所有权,源指针置空
- 所有权转移后,原指针不再持有资源
// 所有权转移示例
std::unique_ptr<int> ptr1(new int(42));
std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权
assert(ptr1 == nullptr);
assert(*ptr2 == 42);2. 自定义删除器
用于管理非 new 分配的资源:
- 必须实现函数调用运算符
operator()(T*) - 删除器类型影响智能指针类型(需作为模板参数)
- 无状态删除器(如函数指针)不增加智能指针大小
- 有状态删除器(如lambda捕获)会使智能指针变大
// 文件句柄管理示例
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) fclose(fp);
std::cout << "File closed\n";
}
};
// 使用自定义删除器
std::unique_ptr<FILE, FileDeleter> filePtr(fopen("test.txt", "r"));
// Lambda表达式作为删除器
auto socketDeleter = [](Socket* s) {
s->shutdown();
delete s;
};
std::unique_ptr<Socket, decltype(socketDeleter)> sockPtr(new Socket(), socketDeleter);3. 最佳实践
- 优先使用
std::make_unique(C++14+)创建实例 - 显式使用
std::move转移所有权,提高代码可读性 - 简单资源使用无捕获lambda,复杂场景使用函数对象
- 删除器应声明为
noexcept,避免抛出异常
4. 常见错误
- 错误:尝试拷贝
unique_ptrstd::unique_ptr<int> a(new int(10)); std::unique_ptr<int> b = a; // 编译错误! - 错误:删除器签名不匹配
// 错误:删除器需要接受 FILE* 而非 void* std::unique_ptr<FILE, void(*)(void*)> p(fopen(...), [](void*){...}); - 错误:在删除器中访问已被释放的成员变量
5. 扩展知识
std::shared_ptr删除器不改变类型,通过控制块存储- 自定义删除器可用于实现:
- 资源池(延迟释放)
- 日志跟踪
- 特殊清理逻辑
- C++17 支持数组特化:
std::unique_ptr<T[]>