如何就地初始化数组?
How to in-place initialize an array?
如何在不复制或移动构造临时元素的情况下初始化数组?当元素具有显式 delete
d 复制或移动构造函数时,仅当元素具有默认构造函数或具有所有默认参数的构造函数时,我才能初始化数组,并且我执行以下操作之一:(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
}
- 这 3 种方式是唯一 方式来初始化没有copying/moving 临时元素的数组吗?
a1
、a2
、a3
算作初始化吗?例如a1
是一个声明,但它的元素得到了初始值,尽管是默认值。
- 它们中有任何错误吗?我用 C++14 标志做了这个 GCC 6.3.0。
- 如果还在复制初始化的范畴下,为什么复制初始化与零初始化结合工作?
- 一般来说,所有数组初始化都是用大括号构造临时元素(除非在没有删除复制或移动构造函数时省略(或者省略不适用于数组?)) 后跟每个元素复制、移动或混合复制和移动构造?
您还可以使用 malloc 创建一个指针,然后在其上使用数组语法(如果 class 是一个 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};
还有一种方法可以将数组初始化为临时值,但我忘了怎么做。
如果 class 是 POD(普通旧数据),您可以直接初始化对象数组。为了使 class 成为 POD,它必须没有构造函数、析构函数或虚方法。 class 中的所有内容也必须声明为 public 才能成为 POD。基本上,POD class 只是一个 c-style 结构,其中可以包含方法。
代码声明Foo a2[2];
声明了一个数组。初始化数组的唯一方法是通过 list-initialization(即零个或多个元素的 brace-enclosed 列表),该行为在标题为 aggregate initialization 的标准部分中进行了描述。 (术语聚合指的是数组,class满足一定条件的数组。
在聚合初始化中,=
的存在没有区别。它的基本定义在 C++14 [dcl.init.aggr]/2:
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
此外,/7:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal-
initializer, from an empty initializer list (8.5.4).
从这里可以看出,copy-initialization 始终用于每个提供的初始化程序。因此,当初始化器是表达式时,class.
必须存在可访问的 copy/move-constructor
但是(正如 Anty 所建议的)您可以将初始化程序设为另一个列表。 Copy-initialization 使用列表称为 copy-list-initialization:
Foo a6[2] = {{6}, {6}};
当单个Foo
为list-initialized时,不是聚合初始化(因为Foo
不是聚合)。所以规则与上面讨论的不同。 non-aggregate class 的 Copy-list-initialization 在 [dcl.init.list]/3.4 中属于 list-initialization,它指定 Foo
中的初始值设定项该列表使用重载决策与构造函数参数匹配。在此阶段将选择 Foo(int)
构造函数,这意味着不需要 copy-constructor。
为了完整起见,我会提到 nuclear option:
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 中,我相信 so-called "guaranteed copy elision" 会将 copy-initialization 更改为实际上不需要 copy/move 构造函数。一旦标准发布,我希望能更新这个答案。草稿中也有一些关于聚合初始化的问题。
在您的情况下,您仍然可以使用这些结构:
Foo a4[2] = {{4},{3}};
或
Foo a5[2] {{4},{3}};
我手边没有 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
}
大括号初始化不是 declare-and-copy,它是单独的语言结构。它很可能只是 in-place 构造元素。我不确定这是否适用的唯一情况是 { Foo(5), Foo(5) }
初始化,因为它明确要求创建临时对象。 { 5, 5}
变体是相同的,因为为了初始化数组,您需要 brace-initalized 个 Foo
对象列表。由于您不创建任何东西,它将使用临时构造函数来获取 { Foo(5), Foo(5) }
。 { { 5 }, { 5 } }
变体编译是因为编译器知道它可以从提供的 { 5 }
初始化程序中构造 Foo
对象,因此不需要临时对象——尽管我不知道允许这样做的确切标准措辞。
不,我认为这些都不是错误。
我记得C++标准中有一行基本上是说编译器在创建新变量时总是可以通过直接初始化来代替赋值初始化。
xx
Foo x( 5 );
Foo x { 5 };
Foo x = { 5 }; // Same as above
- 正如我上面已经指出的:不,您可以 in-place 初始化数组,您只需要一个适当的元素初始化器。
{ 5 }
将被解释为 "an initializer for Foo object",而普通的 5
将被解释为 "a value that can be converted to a temporary Foo object"。初始化器列表通常必须包含元素的初始化器列表或元素的确切类型的项。如果给出不同的东西,将创建一个临时的。
如何在不复制或移动构造临时元素的情况下初始化数组?当元素具有显式 delete
d 复制或移动构造函数时,仅当元素具有默认构造函数或具有所有默认参数的构造函数时,我才能初始化数组,并且我执行以下操作之一:(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
}
- 这 3 种方式是唯一 方式来初始化没有copying/moving 临时元素的数组吗?
a1
、a2
、a3
算作初始化吗?例如a1
是一个声明,但它的元素得到了初始值,尽管是默认值。- 它们中有任何错误吗?我用 C++14 标志做了这个 GCC 6.3.0。
- 如果还在复制初始化的范畴下,为什么复制初始化与零初始化结合工作?
- 一般来说,所有数组初始化都是用大括号构造临时元素(除非在没有删除复制或移动构造函数时省略(或者省略不适用于数组?)) 后跟每个元素复制、移动或混合复制和移动构造?
您还可以使用 malloc 创建一个指针,然后在其上使用数组语法(如果 class 是一个 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};
还有一种方法可以将数组初始化为临时值,但我忘了怎么做。
如果 class 是 POD(普通旧数据),您可以直接初始化对象数组。为了使 class 成为 POD,它必须没有构造函数、析构函数或虚方法。 class 中的所有内容也必须声明为 public 才能成为 POD。基本上,POD class 只是一个 c-style 结构,其中可以包含方法。
代码声明Foo a2[2];
声明了一个数组。初始化数组的唯一方法是通过 list-initialization(即零个或多个元素的 brace-enclosed 列表),该行为在标题为 aggregate initialization 的标准部分中进行了描述。 (术语聚合指的是数组,class满足一定条件的数组。
在聚合初始化中,=
的存在没有区别。它的基本定义在 C++14 [dcl.init.aggr]/2:
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
此外,/7:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal- initializer, from an empty initializer list (8.5.4).
从这里可以看出,copy-initialization 始终用于每个提供的初始化程序。因此,当初始化器是表达式时,class.
必须存在可访问的 copy/move-constructor但是(正如 Anty 所建议的)您可以将初始化程序设为另一个列表。 Copy-initialization 使用列表称为 copy-list-initialization:
Foo a6[2] = {{6}, {6}};
当单个Foo
为list-initialized时,不是聚合初始化(因为Foo
不是聚合)。所以规则与上面讨论的不同。 non-aggregate class 的 Copy-list-initialization 在 [dcl.init.list]/3.4 中属于 list-initialization,它指定 Foo
中的初始值设定项该列表使用重载决策与构造函数参数匹配。在此阶段将选择 Foo(int)
构造函数,这意味着不需要 copy-constructor。
为了完整起见,我会提到 nuclear option:
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 中,我相信 so-called "guaranteed copy elision" 会将 copy-initialization 更改为实际上不需要 copy/move 构造函数。一旦标准发布,我希望能更新这个答案。草稿中也有一些关于聚合初始化的问题。
在您的情况下,您仍然可以使用这些结构:
Foo a4[2] = {{4},{3}};
或
Foo a5[2] {{4},{3}};
我手边没有 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
}
大括号初始化不是 declare-and-copy,它是单独的语言结构。它很可能只是 in-place 构造元素。我不确定这是否适用的唯一情况是
{ Foo(5), Foo(5) }
初始化,因为它明确要求创建临时对象。{ 5, 5}
变体是相同的,因为为了初始化数组,您需要 brace-initalized 个Foo
对象列表。由于您不创建任何东西,它将使用临时构造函数来获取{ Foo(5), Foo(5) }
。{ { 5 }, { 5 } }
变体编译是因为编译器知道它可以从提供的{ 5 }
初始化程序中构造Foo
对象,因此不需要临时对象——尽管我不知道允许这样做的确切标准措辞。不,我认为这些都不是错误。
我记得C++标准中有一行基本上是说编译器在创建新变量时总是可以通过直接初始化来代替赋值初始化。
xx
Foo x( 5 );
Foo x { 5 };
Foo x = { 5 }; // Same as above
- 正如我上面已经指出的:不,您可以 in-place 初始化数组,您只需要一个适当的元素初始化器。
{ 5 }
将被解释为 "an initializer for Foo object",而普通的5
将被解释为 "a value that can be converted to a temporary Foo object"。初始化器列表通常必须包含元素的初始化器列表或元素的确切类型的项。如果给出不同的东西,将创建一个临时的。