使用 std::make_unique 自定义初始化数组

Custom initialize array with std::make_unique

假设我想创建一个 std::unique_ptr<int[]>,但我想将创建的数组初始化为自定义值:{1,2,3,4,5}.

我可以使用 new 并将原始指针传递给 std::unique_ptr 构造函数,后者将拥有并管理它。

std::unique_ptr<int[]> ptr{ new int[5]{1,2,3,4,5} };

我的问题是,可以用 std::make_unique 做同样的事吗?

std::make_unique有 3 个重载:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args );     // (1) for non-array type

template< class T >
unique_ptr<T> make_unique( std::size_t size );   // (2) for array type with unknown bounds

template< class T, class... Args >
/* unspecified */ make_unique( Args&&... args ) = delete; // (3) for array type with known bounds.

None个支持你想要的行为(注意第三个函数被标记为delete)。

可以使用(2)单独初始化数组元素,或者切换到std::vector使用(1).

(特别是 推荐 vector 的部分)是正确的,但我只是想添加另一个选项。

如果数组边界已知且固定,而不是未知边界可变长度 C 样式数组,您可以从 C 样式数组切换到 std::array 以实现此目的。完全打开优化后,运行时工作是等效的(在 -O1g++,它正确地确定它可以内联整个事情,使其成为一个简单的分配,然后填充单个元素直接分配新分配的内存,而不是尝试在堆栈上创建一个 array,然后将其作为参数传递给 make_unique,这最终会调用移动构造函数,有效地将 [=19] 的工作加倍=]).您只需更改:

std::unique_ptr<int[]> ptr{ new int[5]{1,2,3,4,5} };

至:

auto ptr = std::make_unique<std::array<int, 5>>(std::array<int, 5>{1,2,3,4,5});

遗憾的是,对于当前的非实验性功能集,这确实需要重复指定指向的类型(一次构造它,一次定义 make_unique 的模板化类型),因为 make_unique 不接受初始化列表,所以你必须在语法上构造一个临时列表,即使优化器避免了它。对于这种特殊情况,您可以使用 experimental features 来避免重复自己,但它并不漂亮(如果您不使用 using 语句来避免指定名称空间,实际上更长):

auto ptr = std::make_unique<std::array<int, 5>>(std::experimental::make_array(1,2,3,4,5));

std::array 相对于 C 风格数组的主要优点和缺点是,无论哪种方式,最终结果都是 std::unique_ptr<std::array<int, 5>>,而不是 std::unique_ptr<int[]>;一方面,指向的数组的大小永远不会改变(你以后不能用指向 std::array<int, 6> 的指针替换 unique_ptr 的内容),但另一方面,大小是在编译时烘焙,所以你和编译器都知道大小。

由于编译器知道大小,因此在调用基于其参数类型模板化的函数时,您不必手动传递指针和大小。该模板将在编译时专门针对您的精确大小(这允许编译器在循环展开或使用常量循环边界时做出更好的优化选择)而根本不会传递大小。

对于未模板化且需要 C 样式参数的函数(例如,它们需要一个数组,并接收第一个元素的 int* 和长度的 size_t),您只需传递 &ptr[0] 作为指针,ptr->size() 作为长度。由于大小是一个编译时常量,这让你免费获得 DRY(没有在多个地方重复数组的大小,也不是你定义相当无用的命名常量只是为了避免 DRY;大小是类型定义,内联使用,在上下文中有明显的含义),没有性能开销(它应该内联到编译时大小,就像你自己输入大小一样,但如果 array的大小稍后更改)。

同样,为了绝对清楚,这里的正确答案几乎总是“使用 std::vector<int>,这类似于 std::unique_ptr<int[]>

  1. 根据需要自动调整 int[] 的大小
  2. 极大地简化了常见用例(例如初始化、复制、移动等)

当大小没有被主动更改时,std::vector 可以与 C 风格的数组 API 一起使用(传递 vec.data()/&vec[0]/&vec.at(0) 作为指针和 vec.size() 作为长度),你不需要担心管理 resizing/reallocations (当你不能在不放弃访问的情况下使用 realloc 时,这在 C++ 中是一个痛苦delete[])。理论上它可能会慢 tiny 一点,但在 99% 的情况下,它会比任何必须从头重新实现类似 vector 的行为更快(因为 vector 调整为以最大速度“正常工作”,而您自己的代码不太可能经过仔细调整。