复制列表初始化和传统复制初始化之间的任何区别
Any difference between copy-list-initialization and traditional copy-initialization?
除了支持多个参数、不允许缩小转换、匹配构造函数采用std::initializer_list参数外,复制列表初始化与传统的复制初始化还有什么不同?
具体来说,假设有两种用户定义的类型,A
和B
:
class A {...};
class B {...};
B b;
A a1 = {b};
A a2 = b;
A
和B
的什么定义会对这两种初始化形式产生影响?例如,A
和B
是否有某种定义,会使初始化中的一个合法但另一个非法,或者两者合法但语义不同,或者两者因不同原因而非法?
(假设A
没有采用std::initializer_list参数的构造函数。)
编辑:添加一个链接到我的一个有点相关的问题:在带有转换运算符的初始值设定项的情况下,复制列表初始化的假定行为是什么?
class B {};
struct A
{
A(B const&) {}
A(A const&) = delete;
};
B b;
A a1 = {b}; // this compiles
A a2 = b; // this doesn't because of deleted copy-ctor
这是因为复制列表初始化与直接列表初始化相同,但有一种情况除外——如果A(B const&)
是explicit
,前者会失败,而后者会工作。
class B {};
struct A
{
explicit A(B const&) {}
};
int main()
{
B b;
A a1{b}; // compiles
A a2 = {b}; // doesn't compile because ctor is explicit
}
可能,新副本列表初始化的行为被定义为"良好"和一致,但由于向后兼容性,旧副本初始化的"奇怪"行为无法改变
正如您所看到的,此子句中的列表初始化规则对于直接表单和复制表单是相同的
与explicit
相关的差异仅在过载解决方案一章中描述。但对于传统的初始化,直接形式和复制形式是不相同的
传统初始化和大括号初始化是分别定义的,因此总是存在一些(可能是无意的)细微差异。
我可以从标准的摘录中看到差异:
1.已经提到的差异
- 不允许缩小转换范围
- 可以有多个参数
braked语法更喜欢初始值设定项列表构造函数(如果存在):
struct A { A(int i_) : i (i_) {} A(std::initializer_list<int> il) : i (*il.begin() + 1) {} int i; } A a1 = 5; // a1.i == 5 A a2 = {5}; // a2.i = 6
2.骨料的不同行为
对于聚合,您不能使用支撑副本构造函数,但可以使用传统的构造函数。
struct Aggr
{
int i;
};
Aggr aggr;
Aggr aggr1 = aggr; // OK
Aggr aggr2 = {aggr}; // ill-formed
3.存在转换运算符时参考初始化的不同行为
Brace初始化不能使用转换为引用类型的运算符
struct S
{
operator int&() { return some_global_int;}
};
int& iref1 = s; // OK
int& iref2 = {s}; // ill-formed
4.其他类型的对象初始化类类型的对象时的一些细微差异
这些差异在本答案末尾的标准摘录中用[*]标记。
- 旧的初始化使用用户定义的转换序列的概念(特别是,如前所述,需要副本构造函数的可用性)
- 大括号初始化只是在适用的构造函数之间执行重载解析,即大括号初始化不能使用转换为类类型的运算符
这些差异导致了一些不太明显的(对我来说)情况,如
struct Intermediate {};
struct S
{
operator Intermediate() { return {}; }
operator int() { return 10; }
};
struct S1
{
S1(Intermediate) {}
};
S s;
Intermediate im1 = s; // OK
Intermediate im2 = {s}; // ill-formed
S1 s11 = s; // ill-formed
S1 s12 = {s}; // OK
// note: but brace initialization can use operator of conversion to int
int i1 = s; // OK
int i2 = {s}; // OK
5.过载分辨率的差异
- 显式构造函数的不同处理
参见13.3.1.7通过列表初始化进行初始化
在副本列表初始化中,如果选择了
explicit
构造函数,则初始化格式不正确。[注意:这与其他情况(13.3.1.3、13.3.1.4),其中仅转换构造函数被考虑用于复制初始化。此限制仅适用如果此初始化是过载最终结果的一部分决议--尾注]
如果你能看到更多的差异或以某种方式纠正我的答案(包括语法错误),请这样做。
以下是C++标准当前草案的相关(但很长)摘录(我还没有找到将它们隐藏在扰流板下的方法):
所有这些都位于第8.5章Initializers 中
8.5初始化程序
如果初始值设定项是一个(非括号)支撑的初始化列表对象或引用被列表初始化(8.5.4)。
如果目标类型是引用类型,请参见8.5.3。
如果目标类型是字符数组、
char16_t
数组、char32_t
的数组,或wchar_t
的数组,初始化器是字符串文字,见8.5.2。如果初始值设定项是
()
,则对象为值已初始化。否则如果目的地类型是数组,这个程序格式不正确。
如果目标类型是cv合格)类别类型:
如果初始化直接初始化,或者如果是复制初始化源类型的cv不合格版本与或的派生类,目的地的类,构造函数考虑。列举了适用的构造函数(13.3.1.3),以及通过过载分辨率(13.3)选择最佳调用如此选择的构造函数来初始化对象初始值设定项表达式或表达式列表作为其参数。如果没有构造函数应用,或者重载解析不明确,则初始化格式不正确。
[*]否则(即,对于剩余的副本初始化情况),用户定义的转换可以从源类型转换为目标类型的序列类型或(当使用转换函数时)到派生类如13.3.1.4所述列举,最好的是通过过载分辨率(13.3)选择。如果转换不能完成或不明确,则初始化格式不正确。函数selected是以初始值设定项表达式作为其参数调用的;如果该函数是一个构造函数,该调用初始化目的地类型的cv不合格版本。临时的是prvalue。调用的结果(对于constructor case)来直接初始化上面的规则,作为复制初始化。在某些情况下,允许执行通过以下方式消除此直接初始化中固有的复制将中间结果直接构造为对象初始化;参见12.2、12.8。
否则,如果源类型是(可能是cv限定的)类类型,转换函数为考虑。列举了适用的转换函数(13.3.1.5),通过过载分辨率选择最佳(13.3).调用如此选择的用户定义转换进行转换初始化器表达式转换为正在初始化的对象。如果转换无法完成或不明确,初始化为不正规。
否则,对象的初始值initialized是初始值设定项的值(可能已转换)表示如果必要时,将初始值设定项表达式转换为cv不合格目的地类型的版本;没有用户定义的转换考虑。如果无法完成转换,则初始化为不正规。
8.5.3参考。。。
8.5.4列表初始化
类型T的对象或引用的列表初始化定义为如下:
如果
T
是聚合,则聚合初始化为执行(8.5.1)。否则,如果初始值设定项列表没有元素,而
T
是一个具有默认构造函数的类类型,即对象值已初始化。否则,如果
T
是std::initializer_list<E>
,prvalueinitializer_list
对象为如下所述构造并用于初始化对象根据初始化类中对象的规则相同类型(8.5)。[*]否则,如果
T
是类类型,构造函数被考虑在内。适用的构造函数为枚举,并通过过载解决方案选择最佳(13.3,13.3.1.7)。如果需要缩小转换范围(见下文)如果转换任何参数,则程序格式不正确。否则,如果初始值设定项列表具有类型为
E
的单个元素,并且T
不是引用类型,或者其引用类型是与E
相关的引用,对象或引用从该要素;如果需要缩小转换范围(见下文)将元素转换为T
,则程序格式不正确。否则,如果
T
是引用类型,是T
引用类型的临时prvalue是已初始化复制列表还是已初始化直接列表,具体取决于引用的初始化类型,并且引用已绑定这是暂时的。[注意:与往常一样,绑定将失败如果引用类型是对的左值引用,则程序格式错误非常数类型--;尾注]否则,如果初始值设定项列表没有元素,则对对象进行值初始化。
否则,程序就是格式错误的。
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 在引用初始化中使用已删除的复制构造函数进行复制初始化
- 为什么 std::string s = "123" 当不涉及副本时被视为复制初始化?
- 复制初始化:为什么即使关闭了复制省略,也没有调用move或copy构造函数
- 复制初始化与直接初始化已更改
- 为什么在直接初始化和赋值中传递 lambda 而不是在复制初始化中传递 lambda 时会编译?
- 复制初始化 - 从 'int' 类型转换为非标量类型
- 我可以制作一个对象方法,如果单独调用,它将自行修改,但如果在复制初始化期间调用,则会返回一个新对象?
- 如果值来自成员变量,则复制初始化和参考初始化之间的C 差异
- 复制初始化表单 '= {}'
- 对C++所做的更改使复制初始化适用于具有显式构造函数的类
- 显式强制转换、直接初始化和复制初始化之间的行为不同
- 是直接初始化还是复制初始化
- 为什么复制初始化是这样的?为什么需要复制构造函数
- 在复制初始化中,对复制构造函数的调用是显式的还是隐式的
- 复制列表初始化和传统复制初始化之间的任何区别
- 复制初始化和显式构造函数-编译器的差异
- 如何为显式重载构造函数启用复制初始化
- POD变量的直接初始化不起作用,但当将变量推到向量上时,复制初始化起作用
- 为什么类构造函数在通过复制初始化对象时不起作用