std::unique_ptr

来自cppreference.com
< cpp‎ | memory
 
 
工具库
语言支持
类型支持(基本类型、RTTI)
库功能特性测试宏 (C++20)
动态内存管理
程序工具
协程支持 (C++20)
变参数函数
调试支持
(C++26)
三路比较
(C++20)
(C++20)(C++20)(C++20)
(C++20)(C++20)(C++20)
通用工具
日期和时间
函数对象
格式化库 (C++20)
(C++11)
关系运算符 (C++20 中弃用)
整数比较函数
(C++20)(C++20)(C++20)   
(C++20)
交换类型运算
(C++14)
(C++11)
(C++11)
(C++11)
(C++17)
常用词汇类型
(C++11)
(C++17)
(C++17)
(C++17)
(C++11)
(C++17)
(C++23)
初等字符串转换
(C++17)
(C++17)

 
动态内存管理
未初始化内存算法
受约束的未初始化内存算法
分配器
垃圾收集器支持
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)
(C++11)(C++23 前)



 
 
在标头 <memory> 定义
template<

    class T,
    class Deleter = std::default_delete<T>

> class unique_ptr;
(1) (C++11 起)
template <

    class T,
    class Deleter

> class unique_ptr<T[], Deleter>;
(2) (C++11 起)

std::unique_ptr 是一种智能指针,它通过指针持有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象。

在发生下列两者之一时,用关联的删除器释放对象:

  • 管理它的 unique_ptr 对象被销毁。
  • 通过 operator=reset() 赋值另一指针给管理它的 unique_ptr 对象。

对象的释放通过调用 get_deleter()(ptr),用可能由用户提供的删除器进行。默认删除器使用 delete 运算符,它销毁对象并解分配内存。

unique_ptr 亦可以不占有对象,该情况下称它为空 (empty)

std::unique_ptr 有两个版本:

  1. 管理单个对象(例如以 new 分配)
  2. 管理动态分配的对象数组(例如以 new[] 分配)

此类满足可移动构造 (MoveConstructible) 可移动赋值 (MoveAssignable) ,但不满足可复制构造 (CopyConstructible) 可复制赋值 (CopyAssignable)

类型要求
-
Deleter 必须是函数对象 (FunctionObject) 或到函数对象 (FunctionObject) 的左值引用或到函数的左值引用,且可以用 unique_ptr<T, Deleter>::pointer 类型的实参予以调用

注解

只有非 const 的 unique_ptr 能转移被管理对象的所有权给另一 unique_ptr。若对象的生存期由 const std::unique_ptr 所管理,则它被限定在创建指针的作用域中。

std::unique_ptr 常用于管理对象的生存期,包含:

  • 通过保证在正常退出和经由异常退出两者上都进行删除,向处理拥有动态生存期的对象的类和函数提供异常安全性。
  • 向函数传递独占的拥有动态生存期的对象的所有权。
  • 从函数获得独占的拥有动态生存期的对象的所有权。
  • 作为具移动容器的元素类型,例如保有指向动态分配对象的指针的 std::vector(比如当需要多态行为的场合)。

std::unique_ptr 可为不完整类型 T 构造,例如用于改善用作 pImpl 手法中句柄的用途。若使用默认删除器,则 T 必须在代码中调用删除器点处完整,这发生于析构函数、移动赋值运算符和 std::unique_ptrreset 成员函数中。(与之相反,std::shared_ptr 不能从指向不完整类型的裸指针构造,但可于 T 不完整处销毁)。注意若 T 是类模板特化,则把 unique_ptr 用作操作数(如 !p),将因 ADL 而要求 T 的形参完整。

T 是某基类 B派生类,则 std::unique_ptr<T> 可隐式转换std::unique_ptr<B>。产生的 std::unique_ptr<B> 的默认删除器将使用 Boperator deleteB 的析构函数若不是虚函数,将导致未定义行为。注意 std::shared_ptr 表现有别:std::shared_ptr<B> 将使用类型 Toperator delete,而且即使 B 的析构函数非,也会正确删除所持有的对象。

不同于 std::shared_ptrstd::unique_ptr 可通过任何满足可空指针 (NullablePointer) 的定制句柄类型管理对象。例如,这允许通过提供定义了 typedef boost::offset_ptr pointer; 或其他缀饰指针Deleter 来管理位于共享内存的对象。

功能特性测试 标准 功能特性
__cpp_lib_constexpr_memory 202202L (C++23) constexpr std::unique_ptr

成员类型

成员类型 定义
pointer 若该类型存在则为 std::remove_reference<Deleter>::type::pointer,否则为 T*。必须满足可空指针 (NullablePointer)
element_type T,此 unique_ptr 所管理的对象类型
deleter_type Deleter,函数对象或到函数或到函数对象的左值引用,会从析构函数调用

成员函数

构造新的 unique_ptr
(公开成员函数)
析构所管理的对象,如果存在的话
(公开成员函数)
unique_ptr 赋值
(公开成员函数)
修改器
返回一个指向被管理对象的指针,并释放所有权
(公开成员函数)
替换被管理对象
(公开成员函数)
交换被管理对象
(公开成员函数)
观察器
返回指向被管理对象的指针
(公开成员函数)
返回用于析构被管理对象的删除器
(公开成员函数)
检查是否有关联的被管理对象
(公开成员函数)
单对象版本,unique_ptr<T>
解引用指向被管理对象的指针
(公开成员函数)
数组版本,unique_ptr<T[]>
提供到被管理数组的有索引访问
(公开成员函数)

非成员函数

创建管理一个新对象的独占指针
(函数模板)
与另一个 unique_ptrnullptr 进行比较
(函数模板)
输出被管理指针的值到输出流
(函数模板)
特化 std::swap 算法
(函数模板)

辅助类

std::unique_ptr 的散列支持
(类模板特化)

示例

#include <cassert>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <locale>
#include <memory>
#include <stdexcept>
 
// 用于下面运行时多态演示的辅助类
struct B
{
    virtual ~B() = default;
 
    virtual void bar() { std::cout << "B::bar\n"; }
};
 
struct D : B
{
    D() { std::cout << "D::D\n"; }
    ~D() { std::cout << "D::~D\n"; }
 
    void bar() override { std::cout << "D::bar\n"; }
};
 
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}
 
// 用于下面自定义删除器演示的辅助函数
void close_file(std::FILE* fp)
{
    std::fclose(fp);
}
 
// 基于 unique_ptr 的链表演示
struct List
{
    struct Node
    {
        int data;
        std::unique_ptr<Node> next;
    };
 
    std::unique_ptr<Node> head;
 
    ~List()
    {
        // 循环按顺序销毁各列表节点,默认析构函数将会递归调用其 `next` 指针的析构函数,
        // 这在足够大的链表上可能造成栈溢出。
        while (head)
        {
            auto next = std::move(head->next);
            head = std::move(next);
        }
    }
 
    void push(int data)
    {
        head = std::unique_ptr<Node>(new Node{data, std::move(head)});
    }
};
 
int main()
{
    std::cout << "1) 独占所有权语义演示\n";
    {
        // 创建一个(独占)资源
        std::unique_ptr<D> p = std::make_unique<D>();
 
        // 转移所有权给 `pass_through`,而它再通过返回值将所有权转移回来
        std::unique_ptr<D> q = pass_through(std::move(p));
 
        // p 现在是已被移动的“空”状态,等于 nullptr
        assert(!p);
    }
 
    std::cout << "\n" "2) 运行时多态演示\n";
    {
        // 创建派生类资源并通过基类指向它
        std::unique_ptr<B> p = std::make_unique<D>();
 
        // 动态派发如期工作
        p->bar();
    }
 
    std::cout << "\n" "3) 自定义删除器演示\n";
    std::ofstream("demo.txt") << 'x'; // 准备要读取的文件
    {
        using unique_file_t = std::unique_ptr<std::FILE, decltype(&close_file)>;
        unique_file_t fp(std::fopen("demo.txt", "r"), &close_file);
        if (fp)
            std::cout << char(std::fgetc(fp.get())) << '\n';
    } // `close_file()` 于此调用(若 `fp` 为空)
 
    std::cout << "\n" "4) 自定义 lambda 表达式删除器和异常安全性演示\n";
    try
    {
        std::unique_ptr<D, void(*)(D*)> p(new D, [](D* ptr)
        {
            std::cout << "由自定义删除器销毁...\n";
            delete ptr;
        });
 
        throw std::runtime_error(""); // `p` 若为普通指针则此处将泄漏
    }
    catch (const std::exception&)
    {
        std::cout << "捕获到异常\n";
    }
 
    std::cout << "\n" "5) 数组形式的 unique_ptr 演示\n";
    {
        std::unique_ptr<D[]> p(new D[3]);
    } // `D::~D()` 被调用 3 次
 
    std::cout << "\n" "6) 链表演示\n";
    {
        List wall;
        const int enough{1'000'000};
        for (int beer = 0; beer != enough; ++beer)
            wall.push(beer);
 
        std::cout.imbue(std::locale("en_US.UTF-8"));
        std::cout << "墙上有 " << enough << " 瓶啤酒...\n";
    } // 销毁所有啤酒
}

可能的输出:

1) 独占所有权语义演示
D::D
D::bar
D::~D
 
2) 运行时多态演示
D::D
D::bar
D::~D
 
3) 自定义删除器演示
x
 
4) 自定义 lambda 表达式删除器和异常安全性演示
D::D
由自定义删除器销毁...
D::~D
捕获到异常
 
5) 数组形式的 unique_ptr 演示
D::D
D::D
D::D
D::~D
D::~D
D::~D
 
6) 链表演示
墙上有 1,000,000 瓶啤酒...

参阅

拥有共享对象所有权语义的智能指针
(类模板)
(C++11)
std::shared_ptr 所管理对象的弱引用
(类模板)