如何就地初始化数组?

时间:2017-02-18 00:03:13

标签: c++ arrays in-place copy-elision deleted-functions

如何在没有复制或移动构造临时元素的情况下初始化数组?当元素具有显式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
}
  1. 这三种方式方式初始化数组而不复制/移动临时元素吗?
  2. a1a2a3是否计为初始化?例如a1是一个声明,但它的元素是初始值,尽管是默认值。
  3. 他们中的任何一个都是错误的吗?我用C ++ 14标志做了GCC 6.3.0。
  4. 如果复制初始化仍然属于 copy 初始化类别,为什么复制初始化与零初始化一起工作?
  5. 一般情况下,使用花括号的所有数组初始化只是临时元素的构造(除非没有复制或移动构造函数的删除(或者elision不适用于数组?),否则被忽略))通过按元素复制,移动或混合复制和移动构造?

4 个答案:

答案 0 :(得分:4)

在您的情况下,您仍然可以使用这些结构:

Foo a4[2] = {{4},{3}};

Foo a5[2] {{4},{3}};

答案 1 :(得分:3)

代码声明Foo a2[2];声明了一个数组。初始化数组的唯一方法是通过列表初始化(即零括号或多个元素的大括号括起来的列表),行为由标准为aggregate initialization的标准部分描述。 (术语聚合指的是数组,以及符合特定条件的类。)

在聚合初始化中,=的存在没有区别。它的基本定义是在C ++ 14 [dcl.init.aggr] / 2:

  

当初始化程序列表初始化聚合时,如8.5.4中所述,初始化程序列表的元素将作为聚合成员的初始化程序,增加下标或成员顺序。每个成员都从相应的初始化子句复制初始化。

另外,/ 7:

  

如果列表中的初始化子句比集合中的成员少,那么未明确初始化的每个成员都应从其括号或等于初始值初始化,或者,如果没有大括号或等于 -   初始化程序,来自空的初始化程序列表(8.5.4)。

您可以从中看到,每个提供的初始化程序始终使用复制初始化。因此,当初始化程序是表达式时,该类必须存在可访问的复制/移动构造函数。

但是(正如Anty所建议的那样)你可以将初始化程序作为另一个列表。使用列表进行复制初始化称为复制列表初始化:

Foo a6[2] = {{6}, {6}};

当列表初始化单个Foo时,它不是聚合初始化(因为Foo不是聚合)。因此规则与上面讨论的规则不同。非聚合类的复制列表初始化位于[dcl.init.list] /3.4中的列表初始化下,它指定Foo列表中的初始化程序使用重载解析与构造函数参数匹配。在此阶段,将选择Foo(int)构造函数,这意味着不需要复制构造函数。

为了完整起见,我提到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中,我相信所谓的'#34;保证副本省略&#34;将更改复制初始化为实际上不需要复制/移动构造函数。一旦标准发布,我希望能够更新这个答案。在草稿中也有一些小规模的初始化。

答案 2 :(得分:1)

您还可以使用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风格结构。

答案 3 :(得分:1)

我手头没有C ++标准,并且引用它可能是证明我的话的唯一方法。所以,为了回答你的每个问题,我只能说:

  1. 不,这不是全部。我无法为您提供详尽的可能性列表,但我之前确实使用了以下内容:
  2. 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 
    }
    
    1. Brace initalization不是声明和复制,它是单独的语言结构。它可能很好地就地构建元素。我不确定这是否适用的唯一情况是{ Foo(5), Foo(5) }初始化,因为它明确请求创建临时对象。 { 5, 5}变体是相同的,因为为了初始化数组,您需要一个Foo对象的大括号列表。由于您没有创建任何内容,因此它将使用temporaries的构造函数来获取{ Foo(5), Foo(5) }{ { 5 }, { 5 } }变体编译,因为编译器知道它可以从提供的Foo初始化程序中构造{ 5 }对象,因此不需要临时值 - 尽管我不知道允许这样的确切标准措辞。 / p>

    2. 不,我认为这些都不是错误。

    3. 我记得C ++标准中的一行基本上说编译器在创建新变量时总是可以通过直接初始化来替换赋值初始化。

    4. XX

      Foo x( 5 );
      Foo x { 5 };
      Foo x = { 5 }; // Same as above
      
      1. 正如我上面已经指出的:不,你可以就地初始化数组,只需要一个合适的元素初始化器。 { 5 }将被解释为“Foo对象的初始值设定项”,而普通5将作为“可以转换为临时Foo对象的值”。初始化列表通常必须包含元素的初始化列表或元素的确切类型的项。如果给出了不同的东西,则会创建一个临时的。