初始化程序列表中的副本构造

时间:2019-02-02 23:41:24

标签: c++ c++17 initializer-list copy-elision

我正在探索std::intializer_list丑陋世界。

据我对标准的了解:

§11.6.4

  
      
  1. 从初始化器列表构造std :: initializer_list类型的对象,就像实现   生成并实现(7.4)类型为“ N const E的数组”的prvalue,其中N是元素数   在初始化列表中。 该数组的每个元素都使用的相应元素进行复制初始化。   初始化程序列表,并且构造了std :: initializer_list对象以引用该数组。 [注意:A   构造器或为副本选择的转换功能在以下情况下应可访问(第14条)   初始化列表。 —尾注] [...]
  2.   

因此,如果类型E class ,我希望 copy构造函数被调用。


以下类不允许复制构造:

struct NonCopyable {
  NonCopyable() = default;   
  NonCopyable(const NonCopyable&) = delete;
};

我将尝试使用此类实例化一个std::initializer_list

#include <vector>

void foo() {
  std::vector<NonCopyable>{NonCopyable{}, NonCopyable{}};
}

有了g++-8.2 -std=c++14,我得到了预期的编译器错误:

error: use of deleted function 'NonCopyable::NonCopyable(const NonCopyable&)'

完美!


但是,行为随着新标准而改变。

实际上,g++-8.2 -std=c++17进行了编译。

Compiler Explorer Test


我认为最初是因为新标准对copy elision提出了新要求。

但是,更改标准库实现(保持c ++ 17)后,错误会再次出现:

clang-7 -std=c++17 -stdlib=libc++失败:

'NonCopyable' has been explicitly marked deleted here NonCopyable(const NonCopyable&) = delete;

Compiler Explorer Test


那我想念什么?

1)C ++ 17在initializer_list元素的副本构造中是否需要复制删除?

2)为什么libc++实现在这里没有编译?


修改 请注意,在示例g++ -std=c++17(已编译)中,如果我将默认构造函数更改为“用户定义”:

struct NonCopyable {
  NonCopyable();
  NonCopyable(const NonCopyable&) = delete;
};

该程序不再编译(不是由于链接错误)。

Compiler Explorer Example

2 个答案:

答案 0 :(得分:5)

问题在于这种类型:

struct NonCopyable {
  NonCopyable() = default;   
  NonCopyable(const NonCopyable&) = delete;
};

可复制的。因此,作为优化,std::initializer_list仅由数组支持,因此libstdc ++所做的只是将整个内容存储到vector中作为优化。请注意,即使此类型具有已删除的副本构造函数,也可以轻松复制!

这就是为什么当您使用户提供默认构造函数时(仅编写;而不是= default;)突然变得无法编译的原因。这样一来,该类型就不再是可复制的了,因此memcpy路径消失了。

对于这种行为是否正确,我不确定(我怀疑是否必须编译此代码 ?我提交了89164以防万一)。您肯定想要在可微复制的情况下采用 libstdc ++的路径-但也许需要排除这种情况?在任何情况下,您都可以通过另外删除复制分配操作符(您可能仍然想这样做)来实现相同目的-这最终也会导致该类型不能被普通复制。

这在C ++ 14中无法编译,因为您无法构造std::initializer_list-复制初始化需要复制构造函数。但是在保证复制省略的C ++ 17中,std::initializer_list的构造很好。但是实际上构造vector的问题与std::initializer_list完全分开(实际上,这是一条总的红色鲱鱼)。考虑:

void foo(NonCopyable const* f, NonCopyable const* l) {
  std::vector<NonCopyable>(f, l);
}

至少在gcc 4.9起,就可以在C ++ 11中编译。

答案 1 :(得分:1)

  

C ++ 17在initializer_list元素的副本构造中是否需要复制删除?

初始化initializer_list的元素永远不能保证使用“副本构造”。它仅执行复制初始化。复制初始化是否调用复制构造函数完全取决于初始化中发生的事情。

如果您具有可以从int转换的类型,并且您进行了Type i = 5;,则这就是复制初始化。但是它不会调用复制构造函数。而是调用Type(int)构造函数。

是的,initializer_list引用的数组元素的构造易于复制省略。包括C ++ 17的保证省略规则。

话虽这么说,不受这些规则的影响是vector 本身的初始化。 vector必须从initializer_list复制对象,因此它们必须具有可访问的复制构造函数。未知编译器/库实现如何设法解决此问题,但这绝对是不合规格的行为。