在 C++ 中,std::unique_ptr 是 C++ 标准库 <memory> 头文件中提供的一种智能指针。它遵循独占所有权语义,即同一时间内,只能有一个 std::unique_ptr 指向某个对象。当这个 std::unique_ptr 被销毁时,它所指向的对象也会被自动销毁,这有助于避免内存泄漏。
作用
- 自动内存管理:
std::unique_ptr可以在其生命周期结束时自动释放所管理的对象的内存,无需手动调用delete,从而减少了因忘记释放内存而导致的内存泄漏风险。 - 明确所有权:通过独占所有权的特性,使得代码中对象的所有权更加清晰,避免多个指针同时管理同一个对象可能带来的问题,如多次释放内存。
场景
- 管理动态分配的资源:当需要动态分配内存时,使用
std::unique_ptr可以确保在资源不再使用时自动释放。例如,在一个函数中创建一个动态分配的对象,使用std::unique_ptr管理该对象,即使函数提前返回或抛出异常,对象的内存也会被正确释放。 - 作为类的成员变量:当类需要拥有一个动态分配的对象时,使用
std::unique_ptr作为成员变量可以简化类的析构函数,因为不需要手动释放成员对象的内存。 - 在容器中存储动态分配的对象:可以将
std::unique_ptr存储在标准容器(如std::vector)中,实现对动态分配对象的高效管理。
用法举例
1. 基本使用
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
// 创建一个 std::unique_ptr 并指向一个新的 MyClass 对象
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
// 调用对象的成员函数
ptr->doSomething();
// std::unique_ptr 离开作用域,自动释放所管理的对象
return 0;
}
在这个例子中,std::make_unique<MyClass>() 用于创建一个 MyClass 对象,并返回一个 std::unique_ptr 指向该对象。当 ptr 离开 main 函数的作用域时,std::unique_ptr 的析构函数会被调用,从而自动释放所管理的 MyClass 对象的内存。
2. 作为函数参数和返回值
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
// 接受一个 std::unique_ptr 作为参数
void processObject(std::unique_ptr<MyClass> obj) {
obj->doSomething();
}
// 返回一个 std::unique_ptr
std::unique_ptr<MyClass> createObject() {
return std::make_unique<MyClass>();
}
int main() {
std::unique_ptr<MyClass> obj = createObject();
processObject(std::move(obj));
// obj 现在为空,因为所有权已经转移
if (!obj) {
std::cout << "obj is empty" << std::endl;
}
return 0;
}
在这个例子中,createObject 函数返回一个 std::unique_ptr,表示创建并返回一个新的 MyClass 对象的所有权。processObject 函数接受一个 std::unique_ptr 作为参数,当调用 processObject(std::move(obj)) 时,使用 std::move 将 obj 的所有权转移给 processObject 函数的参数,之后 obj 变为空指针。
3. 容器中使用
#include <iostream>
#include <memory>
#include <vector>
class MyClass {
public:
MyClass(int value) : data(value) { std::cout << "MyClass constructor with value: " << data << std::endl; }
~MyClass() { std::cout << "MyClass destructor with value: " << data << std::endl; }
int getData() const { return data; }
private:
int data;
};
int main() {
std::vector<std::unique_ptr<MyClass>> vec;
// 向容器中添加元素
vec.push_back(std::make_unique<MyClass>(1));
vec.push_back(std::make_unique<MyClass>(2));
// 访问容器中的元素
for (const auto& ptr : vec) {
std::cout << "Data: " << ptr->getData() << std::endl;
}
// 容器销毁时,自动释放所有元素的内存
return 0;
}
在这个例子中,std::vector 存储了多个 std::unique_ptr<MyClass> 对象。当向容器中添加元素时,使用 std::make_unique 创建新的 MyClass 对象并将其所有权转移给 std::unique_ptr,然后添加到容器中。当容器销毁时,所有 std::unique_ptr 的析构函数会被调用,从而自动释放所管理的 MyClass 对象的内存。
4. 注意
4.1 独占所有权特性
-
唯一性要求
std::unique_ptr遵循独占所有权语义,同一时间只能有一个std::unique_ptr指向某个对象。这意味着不能直接将一个std::unique_ptr赋值给另一个std::unique_ptr,因为这会破坏独占性。
-
所有权转移
- 若要转移
std::unique_ptr的所有权,需使用std::move函数。调用std::move后,原std::unique_ptr会失去对对象的所有权,变为空指针。
- 若要转移
4.2 避免与原始指针混用
-
手动删除风险
- 若将
std::unique_ptr与手动管理的原始指针混用,可能会导致对象被多次删除,从而引发未定义行为。
- 若将
-
示例代码:
#include <memory>
class MyClass {};
int main() {
MyClass* rawPtr = new MyClass();
std::unique_ptr<MyClass> uniquePtr(rawPtr);
// 错误,手动删除已被 std::unique_ptr 管理的对象
// delete rawPtr;
return 0;
}
-
推荐创建方式
- 建议使用
std::make_unique来创建std::unique_ptr,这样可以避免手动使用new带来的潜在问题,并且代码更加简洁和安全。
- 建议使用
-
示例代码:
#include <memory>
class MyClass {};
int main() {
std::unique_ptr<MyClass> uniquePtr = std::make_unique<MyClass>();
return 0;
}
4.3 自定义删除器
- 使用场景
- 当需要管理非传统的资源(如文件句柄、数据库连接等)时,可使用自定义删除器。自定义删除器可以是函数指针、函数对象或 Lambda 表达式。
- 示例代码:
#include <memory>
#include <cstdio>
class FileDeleter {
public:
void operator()(FILE* file) {
if (file) {
std::fclose(file);
}
}
};
int main() {
FILE* file = std::fopen("test.txt", "w");
std::unique_ptr<FILE, FileDeleter> filePtr(file);
return 0;
}
- 删除器类型影响
- 自定义删除器会影响
std::unique_ptr的类型。在使用自定义删除器时,需要在std::unique_ptr的模板参数中指定删除器的类型。
- 自定义删除器会影响
4.4 空指针检查
-
访问空指针风险
- 在使用
std::unique_ptr之前,应检查其是否为空,避免对空指针进行解引用操作,否则会导致未定义行为。
- 在使用
-
示例代码:
#include <memory>
class MyClass {
public:
void doSomething() {}
};
int main() {
std::unique_ptr<MyClass> ptr;
if (ptr) {
ptr->doSomething();
}
return 0;
}
4.5 异常安全
- 资源管理优势
-
std::unique_ptr能够在异常发生时自动释放所管理的对象,从而保证资源的正确释放,避免资源泄漏。
-
- 示例代码:
#include <memory>
#include <stdexcept>
class MyClass {};
void someFunction() {
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
// 可能会抛出异常的代码
if (true) {
throw std::runtime_error("An error occurred");
}
// 即使抛出异常,ptr 所管理的对象也会被自动释放
}
int main() {
try {
someFunction();
} catch (const std::exception& e) {
// 处理异常
}
return 0;
}