[英]How to in-place initialize an array?
如何在没有复制或移动构造临时元素的情况下初始化数组? 当元素具有显式delete
d复制或移动构造函数时,我只能在元素具有默认ctor或具有所有默认参数的ctor时初始化数组,并且我执行以下操作之一:(a)明确声明数组,( b)直接初始化和零初始化数组,或(c)复制初始化和零初始化数组。 无论是直接(但不是零)初始化,也不是复制(但不是零)初始化编译。
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
//Foo(Foo&&) = delete; // <-- gives same effect
int num;
};
int main()
{
// Resultant arrays for 'a1', 'a2', and 'a3' are two
// 'Foo' elements each with 'num' values of '5':
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization and zero initialization
Foo a3[2] = {}; // copy initialization and zero initialization
Foo a4[2] {5, 5}; // direct initialization -> ERROR
Foo a5[2] = {5, 5}; // copy initialization -> ERROR
}
a1
, a2
和a3
算作初始化吗? 例如, a1
是一个声明,但它的元素会获得初始值,尽管是默认值。 在您的情况下,您仍然可以使用这些结构:
Foo a4[2] = {{4},{3}};
要么
Foo a5[2] {{4},{3}};
代码声明Foo a2[2];
声明一个数组。 初始化数组的唯一方法是通过列表初始化(即零括号或多个元素的大括号括起来的列表),行为由标准的聚合初始化部分描述。 (术语聚合是指数组,以及满足特定条件的类)。
在聚合初始化中, =
的存在没有区别。 它的基本定义是在C ++ 14 [dcl.init.aggr] / 2中:
当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。 每个成员都是从相应的initializer子句复制初始化的。
另外,/ 7:
如果列表中的初始化子条款少于聚合中的成员,则未明确初始化的每个成员应从其大括号或等号初始化程序初始化,或者,如果没有大括号或等于初始化程序,从空的初始化列表(8.5.4)。
您可以从中看到,每个提供的初始化程序始终使用复制初始化。 因此,当初始化程序是表达式时,该类必须存在可访问的复制/移动构造函数。
但是(如Anty所建议的那样)您可以将初始化程序设置为另一个列表。 使用列表进行复制初始化称为复制列表初始化:
Foo a6[2] = {{6}, {6}};
当单个Foo
被列表初始化时,它不是聚合初始化(因为Foo
不是聚合)。 因此规则与上面讨论的规则不同。 非聚合类的复制列表初始化位于[dcl.init.list] /3.4中的列表初始化下,它为Foo
指定列表中的初始化程序使用重载解析与构造函数参数匹配。 在这个阶段,将选择Foo(int)
构造函数,这意味着不需要复制构造函数。
为了完整起见,我将提到核选项 :
typename std::aligned_storage< sizeof(Foo), alignof(Foo) >::type buf[2];
::new ((void *)::std::addressof(buf[0])) Foo(5);
::new ((void *)::std::addressof(buf[1])) Foo(5);
Foo *a7 = reinterpret_cast<Foo *>(buf);
// ...
a7[0].~Foo();
a7[1].~Foo();
显然,当你无法通过任何其他方式实现目标时,这是最后的手段。
注1:以上内容适用于C ++ 14。 在C ++ 17中,我相信所谓的“保证复制省略”会改变复制初始化,实际上不需要复制/移动构造函数。 一旦标准发布,我希望能够更新这个答案。 在草稿中也有一些小规模的初始化。
您还可以使用malloc创建指针,然后在其上使用数组语法(如果类是POD)。 例如:
class A {
public:
int var1;
int var2;
public int add(int firstNum, int secondNum) {
return firstNum + secondNum;
}
}
A * p = 0;
while(!p) {
p = (A*)malloc(sizeof(A) * 2);
}
p[0] = {2, 3};
p[1] = {2, 5};
还有一种方法可以将数组初始化为临时值,但我忘记了如何做到这一点。
如果类是POD(普通旧数据),则可以直接初始化对象数组。 为了使类成为POD,它必须没有构造函数,析构函数或虚方法。 课堂上的所有内容也必须公开,以便它成为POD。 基本上,POD类只是一个c风格的结构,可以有方法。
我手头没有C ++标准,并且引用它可能是证明我的话的唯一方法。 所以,为了回答你的每个问题,我只能说:
XX
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
int num;
};
int main()
{
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization
Foo a3[2] = {}; // also direct initialization
Foo a4[2] { {5}, {5} }; // also direct initialization
Foo a5[2] = { {5}, {5} }; // also direct initialization
}
Brace initalization不是声明和复制,它是单独的语言结构。 它可能很好地就地构建元素。 我不确定这是否适用的唯一情况是{ Foo(5), Foo(5) }
初始化,因为它明确地请求创建临时。 { 5, 5}
变体是相同的,因为为了初始化一个数组,你需要一个Foo
对象的大括号列表。 由于你没有创建任何,它将使用temporaries的构造函数来获得{ Foo(5), Foo(5) }
。 { { 5 }, { 5 } }
变体编译,因为编译器知道它可以用提供的{ 5 }
初始化器构造Foo
对象,因此不需要临时值 - 尽管我不知道允许这种情况的确切标准措辞。
不,我不认为这些都是错误。
我记得C ++标准中的一行,基本上说编译器总是可以在创建新变量时通过直接初始化来替换赋值初始化。
XX
Foo x( 5 );
Foo x { 5 };
Foo x = { 5 }; // Same as above
{ 5 }
将被解释为“Foo对象的初始值设定项”,而plain 5
将作为“可以转换为临时Foo对象的值”进行解释。 初始化列表通常必须包含元素的初始化列表或元素的确切类型的项。 如果给出了不同的东西,将创建一个临时的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.