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

C++ 复制列表初始化和传统的复制初始化之间有什么区别吗?,c++,c++11,C++,C++11,除了支持多个参数、不允许缩小转换、使用std::initializer\u list参数匹配构造函数外,复制列表初始化与传统复制初始化还有什么不同 具体来说,假设有两种用户定义的类型,A和B: class A {...}; class B {...}; B b; A a1 = {b}; A a2 = b; A和B的哪种定义会对这两种初始化形式产生影响?e、 g.a和B是否有某种定义,使得其中一个初始化合法,但另一个非法,或者两者都合法,但语义不同,或者两者都非法,原因不同 (假设A没有采用s

除了支持多个参数、不允许缩小转换、使用std::initializer\u list参数匹配构造函数外,复制列表初始化与传统复制初始化还有什么不同

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

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

B b;
A a1 = {b};
A a2 = b;
A
B
的哪种定义会对这两种初始化形式产生影响?e、 g.
a
B
是否有某种定义,使得其中一个初始化合法,但另一个非法,或者两者都合法,但语义不同,或者两者都非法,原因不同

(假设
A
没有采用std::initializer\u 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&)
been
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
}

可能新副本列表初始化的行为被定义为“良好”和一致,但由于向后兼容,旧副本初始化的“奇怪”行为无法更改。
如您所见,本条款中的列表初始化规则对于直接表单和复制表单是相同的。
显式
相关的差异仅在过载解决方案一章中描述。但对于传统的初始化,直接和复制表单并不相同。
传统初始化和大括号初始化是分开定义的,因此总有一些(可能是无意中)细微的差异

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

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
    

    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列表初始化初始化

    在复制列表初始化中,如果选择了显式构造函数,则 初始化格式不正确。[注:这与其他方法不同 仅转换施工人员的情况(13.3.1.3、13.3.1.4) 复制初始化时考虑。此限制仅适用 如果此初始化是过载最终结果的一部分 决议-结束说明]

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


    以下是相关的(但很长)摘录(我还没有找到一种方法将它们隐藏在扰流板下):
    所有这些都位于第8.5章初始值设定项中

    8.5初始化者

    • 如果初始值设定项是一个(非括号)带括号的init列表,则 对象或引用列表已初始化(8.5.4)

    • 如果目的地类型为参考类型,请参见8.5.3

    • 如果目标类型是字符数组,则为
      char16\t
      的数组
      char32\u t
      数组,或
      wchar\u t
      数组,且初始值设定项为 字符串文字,见8.5.2

    • 如果初始值设定项为
      ()
      ,则对象为 值已初始化

    • 否则,如果目标类型是数组, 这个程序格式不好

    • 如果目标类型是(可能是 cv(合格)等级类型:

      • 如果初始化是 直接初始化,或者如果是复制初始化 源类型的cv非限定版本与类相同,或 派生类,目标类,构造函数是 考虑过的。列举了适用的施工人员(13.3.1.3),以及 通过过载分辨率(13.3)选择最佳。这个 调用如此选择的构造函数来初始化对象,并使用 初始值设定项表达式或表达式列表作为其参数。如果没有 构造函数应用,或者重载解析不明确 初始化格式不正确

      • [*]否则(即 其余的复制初始化情况),用户定义的转换 可以从源类型转换到目标类型的序列 输入或(当使用转换函数时)到派生类 其中按13.3.1.4所述列举,最佳为 通过过载分辨率(13.3)选择。如果无法进行转换 完成或不明确,初始化格式不正确。功能 以初始值设定项表达式作为参数调用selected;如果 函数是一个构造函数,调用初始化 cv目标类型的不合格版本。临时的是 prvalue。结果
        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