std::unique_ptr --- 独占所有权的智能指针

知识点简介
std::unique_ptr 是 C++11 引入的智能指针,用于管理独占所有权 的动态分配对象。同一时刻只能有一个 unique_ptr 拥有某个裸指针,当 unique_ptr 被销毁时,它所管理的对象也会被自动释放。这使得资源管理变得异常简单------你几乎再也不用手动写 delete 了。
unique_ptr 的核心思想是 RAII(Resource Acquisition Is Initialization):资源在构造时获取,在析构时释放。它的大小和裸指针相同(无额外开销),是零成本抽象(zero-cost abstraction)的典范。
核心语法 / 用法说明
1. 创建 unique_ptr
cpp
#include <memory>
// C++14 起推荐使用 std::make_unique(异常安全 + 更简洁)
auto p1 = std::make_unique<int>(42); // 创建一个 int,值为 42
// C++11 兼容写法(需要显式 new)
std::unique_ptr<int> p2(new int(100));
// 管理数组(C++14 起可用 make_unique 创建数组)
auto arr = std::make_unique<int[]>(10); // 创建长度为 10 的 int 数组
2. 所有权转移
cpp
auto p1 = std::make_unique<int>(42);
auto p2 = std::move(p1); // 所有权从 p1 转移到 p2
// 此后 p1 == nullptr,p2 拥有该对象
3. 释放与重置
cpp
auto p = std::make_unique<int>(100);
int* raw = p.release(); // 放弃所有权,返回裸指针,你必须手动 delete
delete raw; // 由于 release 了,必须手动释放
p.reset(new int(200)); // 释放旧对象,接管新对象
p.reset(); // 直接释放,p 变为 nullptr
4. 自定义删除器
cpp
// 自定义删除器:关闭文件
auto file_deleter = [](FILE* f) {
if (f) {
std::fclose(f);
std::cout << "文件已关闭\n";
}
};
std::unique_ptr<FILE, decltype(file_deleter)> filePtr(
std::fopen("test.txt", "w"), file_deleter
);
代码示例
cpp
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// ============================================================
// 示例 1:unique_ptr 基本用法 --- 管理动态对象
// ============================================================
class Widget {
public:
Widget(int id) : m_id(id) {
std::cout << "Widget #" << m_id << " 被构造\n";
}
~Widget() {
std::cout << "Widget #" << m_id << " 被析构\n";
}
void doWork() const {
std::cout << "Widget #" << m_id << " 正在工作...\n";
}
private:
int m_id;
};
void basic_usage() {
std::cout << "=== 示例 1:基本用法 ===\n";
// 工厂函数返回 unique_ptr --- 调用者获得独占所有权
auto createWidget = [](int id) -> std::unique_ptr<Widget> {
return std::make_unique<Widget>(id);
};
auto w = createWidget(1); // 创建 Widget #1
w->doWork(); // 通过 -> 访问成员
// w 离开作用域时自动析构 Widget #1,无需手动 delete
}
// ============================================================
// 示例 2:容器中存放 unique_ptr --- 多态场景
// ============================================================
class Shape {
public:
virtual ~Shape() = default; // 虚析构函数是必须的!
virtual void draw() const = 0;
virtual std::string name() const = 0;
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << " ○ 绘制圆形\n";
}
std::string name() const override { return "Circle"; }
};
class Rectangle : public Shape {
public:
void draw() const override {
std::cout << " □ 绘制矩形\n";
}
std::string name() const override { return "Rectangle"; }
};
void polymorphic_container() {
std::cout << "\n=== 示例 2:多态容器 ===\n";
std::vector<std::unique_ptr<Shape>> shapes;
// 向容器中添加不同类型的 Shape
shapes.push_back(std::make_unique<Circle>());
shapes.push_back(std::make_unique<Rectangle>());
shapes.push_back(std::make_unique<Circle>());
// 统一遍历绘制 --- 多态调用
for (const auto& shape : shapes) {
shape->draw();
}
// 将容器中的元素转移给另一个容器
std::vector<std::unique_ptr<Shape>> otherShapes = std::move(shapes);
std::cout << "原容器大小: " << shapes.size() // 输出 0
<< ", 新容器大小: " << otherShapes.size() // 输出 3
<< "\n";
// 注意:shapes 现在为空,所有元素已转移
}
// ============================================================
// 示例 3:自定义删除器 --- 管理非内存资源
// ============================================================
struct Connection {
int id;
bool isOpen = true;
};
// 模拟打开数据库连接
Connection* openConnection(int id) {
std::cout << " 打开连接 #" << id << "\n";
return new Connection{id, true};
}
// 模拟关闭数据库连接
void closeConnection(Connection* conn) {
if (conn) {
std::cout << " 关闭连接 #" << conn->id << "\n";
conn->isOpen = false;
delete conn;
}
}
void custom_deleter_demo() {
std::cout << "\n=== 示例 3:自定义删除器 ===\n";
{
// 指定删除器为 closeConnection 函数
std::unique_ptr<Connection, decltype(&closeConnection)>
conn(openConnection(42), closeConnection);
std::cout << " 连接 #" << conn->id << " 状态: "
<< (conn->isOpen ? "开启" : "关闭") << "\n";
} // 离开作用域时自动调用 closeConnection
std::cout << " 连接已自动关闭\n";
}
// ============================================================
// 示例 4:unique_ptr 与工厂模式
// ============================================================
enum class VehicleType { Car, Bike };
class Vehicle {
public:
virtual ~Vehicle() = default;
virtual void drive() const = 0;
};
class Car : public Vehicle {
public:
void drive() const override { std::cout << " 🚗 驾驶汽车\n"; }
};
class Bike : public Vehicle {
public:
void drive() const override { std::cout << " 🚲 骑自行车\n"; }
};
// 工厂函数:根据类型返回不同的 Vehicle
// 返回 unique_ptr<Vehicle> 明确表达了"调用者获得所有权"的语义
std::unique_ptr<Vehicle> createVehicle(VehicleType type) {
switch (type) {
case VehicleType::Car:
return std::make_unique<Car>();
case VehicleType::Bike:
return std::make_unique<Bike>();
default:
return nullptr; // 可以返回空指针
}
}
void factory_demo() {
std::cout << "\n=== 示例 4:工厂模式 ===\n";
auto car = createVehicle(VehicleType::Car);
if (car) car->drive();
auto bike = createVehicle(VehicleType::Bike);
if (bike) bike->drive();
}
// ============================================================
// 主函数
// ============================================================
int main() {
basic_usage();
polymorphic_container();
custom_deleter_demo();
factory_demo();
std::cout << "\n所有 unique_ptr 已自动析构,无内存泄漏!\n";
return 0;
}
编译与运行
bash
g++ -std=c++17 -Wall -Wextra -o unique_ptr_demo unique_ptr_demo.cpp
./unique_ptr_demo
预期输出
ini
=== 示例 1:基本用法 ===
Widget #1 被构造
Widget #1 正在工作...
Widget #1 被析构
=== 示例 2:多态容器 ===
○ 绘制圆形
□ 绘制矩形
○ 绘制圆形
原容器大小: 0, 新容器大小: 3
=== 示例 3:自定义删除器 ===
打开连接 #42
连接 #42 状态: 开启
关闭连接 #42
连接已自动关闭
=== 示例 4:工厂模式 ===
🚗 驾驶汽车
🚲 骑自行车
所有 unique_ptr 已自动析构,无内存泄漏!
注意事项 / 常见陷阱
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
| 禁止拷贝 | unique_ptr 的拷贝构造和拷贝赋值都是 = delete 的,因为它是独占所有权 |
使用 std::move() 转移所有权 |
| 不要与裸指针混用 | 如果你把同一个裸指针交给两个 unique_ptr,会发生 double-free |
始终通过 make_unique 或 new 直接初始化 |
| 基类需要虚析构函数 | 当 unique_ptr<Base> 持有 Derived 对象时,如果 Base 没有虚析构,派生类的析构函数不会被调用 |
给基类加上 virtual ~Base() = default; |
数组用 T[] 特化 |
unique_ptr<int> 调用 delete,unique_ptr<int[]> 调用 delete[] |
使用 std::make_unique<int[]>(n) 或 unique_ptr<int[]>(new int[n]) |
| 不要在函数参数中按值传递 unique_ptr | void foo(unique_ptr<T> p) 会强制调用方转移所有权,语义不清晰 |
如果只是想访问对象,传 T& 或 T*;如果确实要转移所有权,传 unique_ptr<T>&& |
| release 后必须手动释放 | p.release() 放弃所有权但不释放资源,返回的裸指针由你负责 |
绝大多数情况用 reset() 而非 release() |
实际应用场景
-
工厂模式 :工厂函数返回
unique_ptr<Base>,调用者获得独占所有权,语义清晰且安全。 -
PIMPL 惯用法(Pointer to Implementation) :用
unique_ptr持有实现类,隐藏实现细节,减少编译依赖。这是 C++ 大型项目中减少编译时间的核心技巧。 -
容器中的多态对象 :
vector<unique_ptr<Base>>可以安全地存放不同类型的派生类对象,离开作用域自动释放。 -
RAII 资源管理:配合自定义删除器,可以管理文件句柄、数据库连接、套接字、锁等非内存资源,统一用 RAII 的方式管理。
-
所有权传递 :在多线程场景中,用
unique_ptr将对象的所有权从一个线程安全地转移到另一个线程(配合std::move),避免共享状态带来的同步问题。
今日小结 :
unique_ptr是现代 C++ 资源管理的基石。记住一个原则------能用unique_ptr就不用shared_ptr,能用智能指针就不用裸指针。明天我们将继续深入,探讨shared_ptr与weak_ptr的共享所有权与循环引用问题。
