我正在探索std::intializer_list
的丑陋世界。
据我对标准的了解:
§11.6.4 :
- 从初始化器列表构造std :: initializer_list类型的对象,就像实现 生成并实现(7.4)类型为“ N const E的数组”的prvalue,其中N是元素数 在初始化列表中。 该数组的每个元素都使用的相应元素进行复制初始化。 初始化程序列表,并且构造了std :: initializer_list对象以引用该数组。 [注意:A 构造器或为副本选择的转换功能在以下情况下应可访问(第14条) 初始化列表。 —尾注] [...]
因此,如果类型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
进行了编译。
我认为最初是因为新标准对copy elision
提出了新要求。
但是,更改标准库实现(保持c ++ 17)后,错误会再次出现:
clang-7 -std=c++17 -stdlib=libc++
失败:
'NonCopyable' has been explicitly marked deleted here NonCopyable(const NonCopyable&) = delete;
那我想念什么?
1)C ++ 17在initializer_list
元素的副本构造中是否需要复制删除?
2)为什么libc++
实现在这里没有编译?
修改
请注意,在示例g++ -std=c++17
(已编译)中,如果我将默认构造函数更改为“用户定义”:
struct NonCopyable {
NonCopyable();
NonCopyable(const NonCopyable&) = delete;
};
该程序不再编译(不是由于链接错误)。
答案 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
复制对象,因此它们必须具有可访问的复制构造函数。未知编译器/库实现如何设法解决此问题,但这绝对是不合规格的行为。