复制初始化和直接初始化的困惑

时间:2016-03-12 20:01:34

标签: c++ initialization implicit-conversion

考虑简单陈述(取自Is there a difference in C++ between copy initialization and direct initialization?):

A c2 = A();
  

此语句将初始化一个临时值然后复制它   值为c2(阅读5.2.3 / 2和8.5 / 14)。 这当然需要一个   非显式复制构造函数(阅读8.5 / 14和12.3.1 / 3和   13.3.1.3/1)

[注意上面的段落中的大胆句子] - >我的问题是为什么?

现在考虑以下代码:

class B {};
struct A 
{
  A(B const&) {}
  A(A const&) = delete;
  //A(A const&); //delete above statement and uncomment this statement, 
  //and everything works, even though there in no body of copy constructor Oo
};

A a2 = B();    //error since there is no copy constructor oO

为什么复制初始化需要复制构造函数的存在,即使上面的代码中没有需要它,

请再请一件事

  

虽然直接初始化可以调用所有构造函数,   此外,它可以进行任何需要匹配的隐式转换   参数类型,复制初始化只需设置一个隐式   转换顺序

[请注意以下段落中的粗体]

这是否意味着直接初始化可以访问所有构造函数并且可以执行隐式转换序列,而复制初始化所有可以执行的是隐式转换序列? 。我的意思是,直接初始化中的隐式转换与复制初始化中的隐式转换序列不同?

2 个答案:

答案 0 :(得分:0)

评估规则

A a1 = B();   // (1) copy-initialization
A a2 = {B()}; // (2) copy-initialization
A a3{B()};    // (3) direct-initialization

来自[dcl.init]/17

  

- 如果初始化程序是(非括号内的) braced-init-list ,则对象或引用将进行列表初始化(8.5.4)。
   - [...]
   - 如果目标类型是(可能是cv限定的)类类型:

     
      
  • 如果初始化是直接初始化,或者如果是复制初始化,那么cv-unqualified   源类型的版本与目标类的类相同,或者是派生类,   构造函数被考虑。 [...]
  •   
  • 否则(即,对于剩余的复制初始化情况),用户定义的转换序列   可以从源类型转换为目标类型或(当转换函数时)   (如果使用)对其派生类进行枚举,如13.3.1.4所述,最好的是   通过重载决议(13.3)选择。 [...]然后根据上述规则,使用调用的结果(这是构造函数的临时情况)进行直接初始化,   作为复制初始化目标的对象。在某些情况下,实施   允许通过构造。来消除这种直接初始化中固有的复制   中间结果直接进入被初始化的对象;见12.2,12.8。
  •   

对于a2a3,初始值设定项是 braced-init-list ,因此我们只进行列表初始化。这最终会调用B const&构造函数。

对于a1,第一个子项不适用 - 因为源类型(B)不是目标类型的相同或派生类(A )。因此,我们进入第二个子项目,涉及考虑转换函数。有一个(A(B const&))所以我们有效地重写了表达式

A a1_new{A{B{}}};

现在通常,这个额外的副本将被省略。但是您明确禁止它,因此代码无法编译。

为什么分化?我不知道。似乎复制初始化应该只是直接初始化的语法糖。毕竟,在大多数情况下,它是......

答案 1 :(得分:0)

(以下内容适用于C ++ 11)为避免漫游过多,A a2 = B();有两个阶段:首先,在编写B时会创建类型为B()的临时对象。然后,此处的函数A(B const&) {}将不会以直接初始化A的方式直接调用 ,因为您使用的是复制初始化语法。如果要调用它直接直接初始化A,则应改写A a2(B()),而不要使用复制初始化语法(这里的魔鬼是=)。下一个是什么?您获得了类型为B的临时对象,并且想用它来初始化类型为A的obj,现在,您可以间接通过转换来初始化A B()的类型为A因此,您的函数A(B const&) {}用作类型转换,而不是直接初始化A。

由于进行了转换,因此创建了类型为A的临时obj,然后我们需要复制构造函数使用该临时obj初始化a2

在副本初始化中,无法调用带有explicit关键字的类型转换函数。 explicit关键字的设计原理是您应该直接调用它。与类型转换功能结合使用时,那些explicit属性函数不应用于隐式类型转换。在直接初始化中,即使它们是explicit,也都可以调用它们。

为了使事情变得有趣,如果您创建副本构造函数并移动构造函数explicit,则甚至无法编写T obj = T{},但是可以编写T obj {T{}}。您也可以查看复制和移动ctor作为转换,只有目标和源类型属于同一类。

我想为您提供更多的阅读材料here。之后,请阅读this question以了解有关复制列表初始化的信息,以及this question以了解有关聚合类型的信息。