unique_ptr 在 C++14 中的优化

1. unique_ptr

在 C++11 引入智能指针(std::unique_ptr, std::shared_ptr, std::weak_ptr)奠定了现代 C++ 内存管理的基础后,C++14 主要在易用性、性能和功能细节上进行了重要的优化和增强。

在 C++11 中,虽然智能指针 std::unique_ptr 是标准的一部分,但创建它的辅助函数 std::make_unique 直到 C++14 才加入。

1.1 在 C++11 中创建对象

C++11 中,创建一个unique_ptr类型的对象我们有以下几种选择:

  • 方案 A:手动实现 make_unique

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <memory>
    #include <iostream>

    // C++11 兼容的 make_unique 实现
    template<typename T, typename... Args>
    std::unique_ptr<T> make_unique(Args&&... args)
    {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
    }

    int main()
    {
    auto ptr = make_unique<int>(42);
    std::cout << "ptr value: " << *ptr.get() << std::endl;
    return 0;
    }
  • 方案 B:直接使用 new

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <memory>
    #include <iostream>

    struct Widget
    {
    int id;
    std::string name;

    Widget(int i, const std::string& n) : id(i), name(n)
    {
    std::cout << "Widget " << id << " created" << std::endl;
    }
    };

    int main()
    {
    std::unique_ptr<Widget> ptr1(new Widget(1, "First"));
    std::unique_ptr<int> ptr2(new int(19));
    return 0;
    }

1.2 C++14 中的原生支持

在 C++14 中,标准库中提供了 std::make_unique,它已经已经成为 <memory> 头文件的一部分,可以直接使用,无需额外实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <memory>
#include <iostream>

struct Widget
{
int id;
std::string name;

Widget() = default;
Widget(int i, const std::string& n) : id(i), name(n)
{
std::cout << "Widget " << id << " created" << std::endl;
}
};

int main()
{
// C++14: 使用 make_unique
auto ptr1 = std::make_unique<int>(100);
auto ptr2 = std::make_unique<Widget>(1, "First");

auto ptr3 = std::make_unique<int[]>(9);
auto ptr4 = std::make_unique<Widget[]>(3); // 创建数组
return 0;
}

使用 std::make_unique 相比于直接使用 new 有以下优势:

  1. 更好的异常安全性
  2. 代码更简洁
  3. 避免手动 new delete
  4. 支持数组版本 std::make_unique<T[]>

std::make_unique 用于创建数组(即 make_unique<T[]>(n))时:

  1. 它会分配足够存放 nT 对象的内存。
  2. 它会对数组中的每一个元素调用默认构造函数 T()(值初始化)。
  3. 不支持使用括号 () 传递参数来初始化数组中的每个元素

当使用 std::make_unique<int[]> 时,模板参数 T 被推导为 int[]std::unique_ptr 针对数组类型(T[])有部分特化实现。这个特化版本在析构时会自动调用 delete[],而不需要(也不能)手动指定删除器。

  • 使用 make_unique<int[]> (自动使用默认数组删除器)

    1
    2
    3
    // 编译器知道这是一个数组,std::unique_ptr<int[]> 的析构函数会自动调用 delete[]
    auto ptr = std::make_unique<int[]>(10);
    // ptr 的类型是 std::unique_ptr<int[]>
  • 直接使用 unique_ptr (自动使用默认数组删除器)

    1
    2
    3
    // 只要模板参数写的是 int[],它就会自动匹配数组特化版本,自动调用 delete[]
    // 不需要写 std::default_delete<int[]>()
    std::unique_ptr<int[]> ptr(new int[10]);
  • 如果不小心写成了 unique_ptr<int> (不会使用数组删除器)

    1
    2
    3
    // 这里类型是 int (单个对象),不是 int[]
    // 析构时会调用 delete (而不是 delete[])!这会导致未定义行为(通常是内存泄漏或崩溃)
    std::unique_ptr<int> ptr(new int[10]);

所以在 C++14 及以后,只要使用 make_unique 创建数组,就不用担心删除器的问题,它是默认提供并且可以正确工作的。

关于智能指针,虽然 C++14 引入了 std::make_unique<T[]>(size_t) 来支持数组,但标准委员会并没有为 std::make_shared 添加类似的数组支持。如果你在 C++14 环境下运行这行代码:

1
auto ptr5 = std::make_shared<int[]>(12);

编译器会报错,提示 make_shared 没有匹配 int[] 类型的重载模板,或者提示 shared_ptr 不支持数组类型。如果需要在 C++14 中用共享指针管理数组,必须手动指定删除器,并且类型要写成 shared_ptr<int> 而不是 shared_ptr<int[]>

1
2
3
4
5
// C++14 正确写法:需要指定删除器
std::shared_ptr<int> ptr5(new int[12], std::default_delete<int[]>());

// 访问元素时需要使用 .get()
ptr5.get()[0] = 100;