在C++17之前,手动管理动态分配的内存是一项常见的错误来源,导致内存泄漏、悬空指针等问题。std::unique_ptr 是一种智能指针,能够自动管理资源的生命周期,确保在作用域结束时自动释放。下面从使用场景、构造方式、转移所有权、与自定义删除器、以及在容器中的使用等方面,系统讲解如何正确使用 std::unique_ptr。
1. 基本使用
#include <memory>
#include <iostream>
struct Resource {
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
};
int main() {
std::unique_ptr <Resource> ptr = std::make_unique<Resource>();
// 资源自动释放
}
make_unique是推荐的构造方式,避免手动new。unique_ptr只能拥有单一指针,复制被禁止,转移通过std::move。
2. 转移所有权
std::unique_ptr <Resource> foo() {
return std::make_unique <Resource>(); // NRVO 或移动语义
}
std::unique_ptr <Resource> bar() {
std::unique_ptr <Resource> ptr = std::make_unique<Resource>();
return ptr; // 移动返回
}
- 通过
std::move可以显式转移:
std::unique_ptr <Resource> a = std::make_unique<Resource>();
std::unique_ptr <Resource> b = std::move(a);
- 转移后
a变为nullptr,不再拥有资源。
3. 自定义删除器
在默认情况下 unique_ptr 用 delete 释放对象。若需要特殊释放逻辑,例如关闭文件句柄或网络连接,可自定义删除器:
struct FileCloser {
void operator()(FILE* fp) const {
if (fp) fclose(fp);
}
};
std::unique_ptr<FILE, FileCloser> filePtr(fopen("log.txt", "w"));
- 自定义删除器可以是函数指针、函数对象、或 lambda。
auto deleter = [](int* p){ delete[] p; };
std::unique_ptr<int[], decltype(deleter)> arr(new int[10], deleter);
4. 与数组配合
std::unique_ptr<int[]> arr(new int[10]); // 注意使用[]
int val = arr[3];
- 对于数组,不能使用
delete,需要delete[],智能指针通过模板参数int[]自动处理。
5. 与标准容器配合
unique_ptr 可以存放在 std::vector、std::list 等容器中:
std::vector<std::unique_ptr<Resource>> vec;
vec.push_back(std::make_unique <Resource>());
- 由于
unique_ptr不可复制,容器只能移动元素。 - 删除元素时,容器会自动销毁对应的
unique_ptr,进而释放资源。
6. 与共享所有权的区别
std::unique_ptr:单一所有权,转移后原指针失效。适合局部资源或父子关系。std::shared_ptr:共享所有权,引用计数。适合跨线程共享或多对象引用。
7. 小技巧与注意事项
- 避免裸
new:始终使用std::make_unique或std::make_shared。 - 不需要
delete:手动释放会导致二次释放错误。 - 保持
nullptr:在转移后及时检查ptr == nullptr。 - 自定义删除器要匹配分配方式:例如
new[]必须用delete[]。 - 不要在
unique_ptr中存放裸指针:如 `std::unique_ptr ` 里存 `int*`,若外部同时持有指针可能导致双重删除。
8. 典型应用示例
8.1 资源池
class Connection {
public:
Connection() { /* 连接初始化 */ }
~Connection() { /* 关闭连接 */ }
};
class ConnectionPool {
std::vector<std::unique_ptr<Connection>> pool;
public:
std::unique_ptr <Connection> acquire() {
if (pool.empty()) return std::make_unique <Connection>();
auto conn = std::move(pool.back());
pool.pop_back();
return conn;
}
void release(std::unique_ptr <Connection> conn) {
pool.push_back(std::move(conn));
}
};
8.2 工厂函数
std::unique_ptr <Animal> createAnimal(const std::string& type) {
if (type == "cat") return std::make_unique <Cat>();
if (type == "dog") return std::make_unique <Dog>();
return nullptr;
}
工厂返回 unique_ptr,调用者立即获得资源所有权,避免忘记释放。
9. 结语
std::unique_ptr 是现代 C++ 资源管理的核心工具,正确使用它可以极大降低内存泄漏风险,提高代码安全性与可读性。记住:
- 用
make_unique或make_shared。 - 通过
std::move转移所有权。 - 关注自定义删除器与数组的特殊处理。
- 与标准容器配合时利用移动语义。
在日常项目中,养成使用 unique_ptr 的习惯,几乎可以消除手工内存管理的烦恼。祝编码愉快!