自定义初始化数组与 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.

它们都不支持您想要的行为(请注意,第三个函数标记为delete)。

您可以使用 (2) 并单独初始化数组的元素,也可以切换到std::vector并使用 (1)。

hgminh的答案(特别是如果可能的话推荐vector的部分)是正确的,但我只是想添加另一个选项。

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

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不接受初始值设定项列表,因此您必须在语法上构造临时,即使优化器避免它。对于这种特殊情况,您可以使用实验性功能来避免重复自己,但它并没有更漂亮(如果您不使用using语句来避免指定命名空间,实际上更长):

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

与 C 样式数组相比,std::array的主要优点和缺点是,无论哪种方式,最终结果都是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()作为长度),并且您无需担心管理大小调整/重新分配(当您不能在不放弃访问delete[]的情况下使用realloc时,这在C++中很痛苦)。从理论上讲,它可以稍微慢一点,但在 99% 的情况下,它会比必须从头开始重新实现类似vector行为的任何行为都要快(因为vector被调整为以最大速度"正常工作",而您自己的代码不太可能经过仔细调整)。

最新更新