繁体   English   中英

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

[英]Any difference between copy-list-initialization and traditional copy-initialization?

除了支持多个参数,不允许缩小转换范围,使用std :: initializer_list参数匹配构造函数之外,与传统的复制初始化相比,复制列表初始化还有什么不同?

具体来说,假设有两种用户定义的类型AB

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

B b;
A a1 = {b};
A a2 = b;

AB哪种定义将对这两种初始化形式有所不同? 例如,是否有对AB的某种定义,从而使一个初始化合法但另一个非法,或者既合法又具有不同的语义,或者既非法又具有不同的原因?

(假设A没有使用std :: initializer_list参数的构造函数。)

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

可能将新的复制列表初始化的行为定义为“良好”且一致,但是由于向后兼容,旧的复制初始化的“怪异”行为无法更改。
如您所见,此子句中列表初始化的规则对于直接和复制形式是相同的。
仅在有关过载解决的章节中描述了与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 


2.聚合的不同行为

对于聚合,您不能使用大括号的复制构造函数,但可以使用传统的。

    struct Aggr
    {
        int i;
    };

    Aggr aggr;
    Aggr aggr1 = aggr; // OK
    Aggr aggr2 = {aggr}; // ill-formed


3.在存在转换运算符的情况下引用初始化的不同行为

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

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初始化器一章中

8.5初始化器

  • 如果初始化程序是一个(非括号括起来的) braced-init-list ,则该对象或引用将被列表初始化(8.5.4)。

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

  • 如果目标类型是字符数组, char16_t数组, char32_t数组或wchar_t数组,并且初始化程序是字符串文字,请参见8.5.2。

  • 如果初始化器为() ,则对象将被值初始化。

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

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

    • 如果初始化是直接初始化,或者如果复制是初始化,其中源类型的cv不合格版本与目标的类相同,或为该类的派生类,则考虑构造函数。 列举了适用的构造函数(13.3.1.3),并通过重载分辨率(13.3)选择了最佳的构造函数。 如此选择的构造函数将以初始化器表达式或expression-list作为其自变量来初始化对象。 如果没有构造函数适用,或者重载解决方案不明确,则初始化格式错误。

    • [*]否则(即,对于其余的复制初始化情况),将枚举可以从源类型转换为目标类型或(当使用转换功能时)转换为其派生类的用户定义转换序列在13.3.1.4中,最好的方法是通过过载分辨率(13.3)选择。 如果转换无法完成或模棱两可,则初始化格式错误。 所选函数以初始化器表达式作为参数进行调用; 如果函数是构造函数,则调用将初始化目标类型的cv不合格版本的临时版本。 临时变量是一个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是具有默认构造函数的类类型,则该对象将被值初始化。

  • 否则,如果Tstd::initializer_list<E> ,则按如下所述构造prvalue initializer_list对象,并根据从相同类型的类(8.5)初始化对象的规则将其用于初始化对象。 。

  • [*]否则,如果T是类类型,则考虑构造函数。 列举了适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择了最佳的构造函数。 如果需要变窄的转换(请参见下文)以转换任何参数,则程序格式错误。

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

  • 否则,如果T是引用类型,一个prvalue临时被引用的类型的T是副本列表初始化或直接列表初始化,根据其种类初始化为基准,参考绑定到暂时的。 [ 注意:与往常一样,如果引用类型是对非const类型的左值引用,则绑定将失败并且程序格式错误。 —尾注 ]

  • 否则,如果初始化器列表中没有元素,则该对象将被值初始化。

  • 否则,程序格式不正确。


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

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
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM