复制列表初始化和传统复制初始化之间有什么区别?

goo*_*era 11 c++ c++11

除了支持多个参数,禁止缩小转换,匹配构造函数采用std :: initializer_list参数,复制列表初始化与传统复制初始化有什么不同?

具体而言,假设有两种用户定义的类型,A并且B:

class A {...};
class B {...};

B b;
A a1 = {b};
A a2 = b;
Run Code Online (Sandbox Code Playgroud)

对这两种初始化形式有什么样的定义AB会有所作为?例如,是否存在某种定义,A并且B会使其中一种初始化合法但另一种是非法的,或者是合法的但具有不同的语义,或者两者都是非法的,具有不同的原因?

(假设A没有构造函数采用std :: initializer_list参数.)

编辑:添加链接到我的一个有点相关的问题:在具有转换运算符的初始化程序的情况下,复制列表初始化的假设行为是什么?

use*_*887 11

可能,新副本列表初始化的行为被定义为"良好"且一致,但由于向后兼容性,旧复制初始化的"怪异"行为无法更改.
正如您所看到的,本节中列表初始化的规则对于直接和复制表单是相同的.
与之相关的差异explicit仅在有关重载决策的章节中进行了描述.但对于传统的初始化,直接和复制形式并不相同.
传统和大括号初始化是分开定义的,因此总有一些(可能是非预期的)微妙差异的可能性.

我可以从标准的摘录中看到的差异:

1.已经提到过差异

  • 缩小转换率是不允许的
  • 多个参数是可能的
  • 如果它们存在,则支持语法更喜欢初始化列表构造函数:

    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
    
    Run Code Online (Sandbox Code Playgroud)


2.聚合的不同行为

对于聚合,您不能使用braced copy-constructor,但可以使用传统的.

    struct Aggr
    {
        int i;
    };

    Aggr aggr;
    Aggr aggr1 = aggr; // OK
    Aggr aggr2 = {aggr}; // ill-formed
Run Code Online (Sandbox Code Playgroud)


3.存在转换运算符时参考初始化的不同行为

Brace初始化不能使用转换为引用类型的运算符

struct S
{
    operator int&() { return some_global_int;}
};

int& iref1 = s; // OK
int& iref2 = {s}; // ill-formed
Run Code Online (Sandbox Code Playgroud)


4.由其他类型的对象初始化类类型的对象的一些细微差别

这些差异在本答案末尾的标准摘录中以[*]标记.

  • 旧的初始化使用用户定义的转换序列的概念(特别是,需要复制构造函数的可用性,如上所述)
  • Brace初始化只是在适用的构造函数之间执行重载解析,即大括号初始化不能使用转换为类类型的运算符

这些差异是造成一些不太明显(对我而言)的案例的原因

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
Run Code Online (Sandbox Code Playgroud)


5.重载分辨率的差异

  • 显性构造函数的不同处理

请参见13.3.1.7按列表初始化初始化

在copy-list-initialization中,如果explicit选择了构造函数,则初始化是错误的.[ 注意:这与其他情况(13.3.1.3,13.3.1.4)不同,其中只考虑转换构造函数进行复制初始化.此限制仅适用于此初始化是重载解析的最终结果的一部分.- 结束说明 ]

如果你能看到更多的差异或以某种方式纠正我的答案(包括语法错误),请做.


以下是当前C++标准草案的相关(但很长)摘录(我没有找到隐藏它们的方法):
所有这些都位于章节8.5初始化程序中

8.5初始化器

  • 如果初始化程序是(非括号的)braced-init-list,则对象或引用是列表初始化的(8.5.4).

  • 如果目标类型是引用类型,请参见8.5.3.

  • 如果目标类型是字符数组,数组char16_t,数组char32_t或数组wchar_t,并且初始值设定项是字符串文字,请参见8.5.2.

  • 如果初始化程序是(),则对象进行值初始化.

  • 否则,如果目标类型是数组,则程序格式错误.

  • 如果目标类型是(可能是cv限定的)类类型:

    • 如果初始化是直接初始化,或者它是复制初始化,其中源类型的cv-nonqualified版本与目标类相同的类或派生类,则考虑构造函数.列举了适用的构造函数(13.3.1.3),并通过重载解析(13.3)选择最佳构造函数.调用所选的构造函数来初始化对象,初始化表达式或表达式列表作为其参数.如果没有构造函数适用,或者重载决策是不明确的,则初始化是错误的.

    • [*]否则(即,对于剩余的复制初始化情况),可以如上所述枚举可以从源类型转换为目标类型或(当使用转换函数时)到其派生类的用户定义的转换序列.在13.3.1.4中,通过重载决策(13.3)选择最好的一个.如果转换不能完成或不明确,则初始化是错误的.选择的函数以初始化表达式作为参数调用; 如果函数是构造函数,则调用初始化目标类型的cv-nonqualified版本的临时函数.临时是一个prvalue.然后,根据上面的规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象.在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除此直接初始化中固有的复制; 见12.2,12.8.

    • 否则,如果源类型是(可能是cv限定的)类类型,则考虑转换函数.列举了适用的转换函数(13.3.1.5),并通过重载决策(13.3)选择最佳函数.调用如此选择的用户定义转换以将初始化表达式转换为正在初始化的对象.如果转换不能完成或不明确,则初始化是错误的.

  • 否则,正在初始化的对象的初始值是初始化表达式的(可能已转换)值.如有必要,将使用标准转换(第4节)将初始化表达式转换为目标类型的cv非限定版本; 不考虑用户定义的转换.如果无法进行转换,则初始化不正确.


8.5.3参考文献......


8.5.4列表初始化

列表初始化对象或类型T的引用定义如下:

  • 如果T是聚合,则执行聚合初始化(8.5.1).

  • 否则,如果初始化列表没有元素并且T是具有默认构造函数的类类型,则对象将进行值初始化.

  • 否则,如果T是特化 std::initializer_list<E>,initializer_list则如下所述构造prvalue 对象,并用于根据相同类型的类初始化对象的规则初始化对象(8.5).

  • [*]否则,如果T是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数.如果转换任何参数需要缩小转换(见下文),则程序格式错误.

  • 否则,如果初始化列表具有单个元素类型E且且T不是引用类型或其引用类型与引用相关E,则从该元素初始化对象或引用; 如果将元素转换为需要缩小转换(见下文)T,则程序格式不正确.

  • 否则,如果 T是引用类型,则引用类型的prvalue临时值T 是copy-list-initialized或direct-list-initialized,具体取决于引用的初始化类型,并且引用绑定到该临时值.[ 注意:像往常一样,如果引用类型是非const类型的左值引用,则绑定将失败并且程序格式错误.- 结束说明  ]

  • 否则,如果初始化列表没有元素,则对象进行值初始化.

  • 否则,该计划是不正确的.



Pra*_*ian 9

复制初始化始终考虑复制构造函数的可用性,而复制列表初始化则不会.

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
Run Code Online (Sandbox Code Playgroud)

这是因为副本列表初始化是相同的直接列表初始化,除了在一种情况下-已经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
}
Run Code Online (Sandbox Code Playgroud)

  • WTF!那真是怪了.在任何一种情况下,为什么要建立一个"B"来关心复制文件是否可访问?愚蠢的语言...... (3认同)
  • @Lightness:因为`T a = b;`相当于`T a(T(b));`(忽略MVP). (2认同)