Appearance
std::unique_ptr 详解
一、概念
unique_ptr 表示独占所有权(exclusive ownership)的动态对象。同一时刻只能有一个 unique_ptr 拥有该对象;离开作用域即自动删除。
cpp
std::unique_ptr<int> up = std::make_unique<int>(42);
// 不可拷贝:std::unique_ptr<int> up2 = up; // ❌ 编译错误
std::unique_ptr<int> up2 = std::move(up); // ✅ 所有权转移,up 变空- 之所以设计为不可拷贝只能移动:一份资源同时被两个独占对象拥有会导致 double-free。
- 头文件
<memory>。 - 底层对象比
shared_ptr轻:无引用计数、无控制块,通常就一个裸指针大小。
二、核心特性
支持数组
std::unique_ptr<T[]> 特化用于数组,析构时用 delete[],随机访问 up[i]:
cpp
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 7;自定义删除器
cpp
auto deleter = [](FILE* f){ if (f) std::fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> fp(std::fopen("a.txt","r"), deleter);删除器类型是模板参数一部分,不同删除器类型是不同的 unique_ptr 类型(不参与运行时开销)。
常用方法
get():获取裸指针(不转让所有权)。release():释放所有权,返回裸指针,调用方负责 delete。reset(new_ptr):替换被管理对象(旧对象被删除)或置空。operator bool:判空。*up/up->method访问对象。swap交换。
何时转移所有权?
工厂函数返回 unique_ptr 作为所有权出口:
cpp
std::unique_ptr<Foo> createFoo() { return std::make_unique<Foo>(); }函数参数若想「借读」用 Foo*/const Foo&;想「接管」用 unique_ptr<Foo> 值参(需 move)。
三、高频问答
Q1. unique_ptr 能拷贝吗?为什么?
不能(拷贝构造/拷贝赋值被 =delete)。原因:独占语义——若可拷贝,两个对象会指向同一资源,析构时 double free。只能 std::move 转移所有权。
Q2. unique_ptr 与 shared_ptr 如何互相转换?
unique_ptr→shared_ptr:允许(shared_ptr<T> sp(std::move(up));),因为独占可自然升级为共享。shared_ptr→unique_ptr:不允许直接从 shared_ptr 构造 unique_ptr(无法剥夺其它持有者的所有权);需确认自己是唯一持有者再用shared_ptr::get(危险)或设计时避免。
Q3. make_unique 的作用?
cpp
auto p = std::make_unique<T>(args...); // 推荐
auto p = std::unique_ptr<T>(new T(args...)); // 间接- C++14 加入
make_unique。 - 异常安全:
f(unique_ptr<T>(new T), g())若g()先求值抛异常会泄漏 new 出的裸指针;make_unique把对象创建与包装绑定,避免此类泄漏窗口。 - 单纯用
new+ 构造 unique_ptr 分两步,中间被异常打断就泄漏。
四、手写 unique_ptr(高频)
考察点:禁拷贝、支持移动、RAII 析构、operator*/->、reset/release,以及数组与删除器的扩展能力。
cpp
#include <utility>
template <typename T>
class MyUniquePtr {
public:
MyUniquePtr(T* p = nullptr) : ptr_(p) {}
~MyUniquePtr() { delete ptr_; }
// 禁拷贝
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
// 支持移动
MyUniquePtr(MyUniquePtr&& o) noexcept : ptr_(o.ptr_) {
o.ptr_ = nullptr;
}
MyUniquePtr& operator=(MyUniquePtr&& o) noexcept {
if (this != &o) {
reset(o.release());
}
return *this;
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; }
T* release() {
T* tmp = ptr_;
ptr_ = nullptr;
return tmp; // 调用方负责 delete
}
void reset(T* p = nullptr) {
T* old = ptr_;
ptr_ = p;
delete old;
}
private:
T* ptr_;
};手写要点 / 追问点
- 禁拷贝:把拷贝构造/赋值
= delete(否则两个对象 double free)。 - move 转移:移动后把源置空,避免源析构时 delete 已转移资源。
- 移动赋值:先
reset(o.release())——旧资源被删、新资源接管、源置空,三步一次搞定且安全。 - 完整标准库版还支持:自定义删除器(类型参数化)、数组特化、
operator[]等。面试说「能扩展删除器与数组」即为加分项。 - 为什么手写 unique_ptr 比手写 shared_ptr 简单?—— 没有引用计数/控制块/线程同步。