Skip to content

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_ptrshared_ptr允许shared_ptr<T> sp(std::move(up));),因为独占可自然升级为共享。
  • shared_ptrunique_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_;
};

手写要点 / 追问点

  1. 禁拷贝:把拷贝构造/赋值 = delete(否则两个对象 double free)。
  2. move 转移:移动后把源置空,避免源析构时 delete 已转移资源。
  3. 移动赋值:先 reset(o.release())——旧资源被删、新资源接管、源置空,三步一次搞定且安全。
  4. 完整标准库版还支持:自定义删除器(类型参数化)、数组特化、operator[] 等。面试说「能扩展删除器与数组」即为加分项。
  5. 为什么手写 unique_ptr 比手写 shared_ptr 简单?—— 没有引用计数/控制块/线程同步。

C++ 面试八股 · VitePress 版