调用C ++ Copy构造函数而不是initializer_list<>

时间:2016-02-08 15:18:01

标签: c++ copy-constructor initializer-list list-initialization

基于此代码

struct Foo 
{
   Foo() 
   {
       cout << "default ctor" << endl;
   }

   Foo(std::initializer_list<Foo> ilist) 
   {
       cout << "initializer list" << endl;
   }

   Foo(const Foo& copy)
   {
       cout << "copy ctor" << endl;
   }
};

int main()
{

   Foo a;
   Foo b(a); 

   // This calls the copy constructor again! 
   //Shouldn't this call the initializer_list constructor?
   Foo c{b}; 



   _getch();
   return 0;
}

输出结果为:

默认ctor

复制ctor

复制ctor

在第三种情况下,我将b放入大括号初始化,这应该调用initializer_list&lt;&gt;构造

相反,复制构造函数起带头作用。

你们有人会告诉我这是如何运作的,为什么?

2 个答案:

答案 0 :(得分:19)

正如Nicol Bolas所指出的,这个答案的原始版本是不正确的:在编写本文时,cppreference错误地记录了在列表初始化中考虑构造函数的顺序。以下是使用n4140标准草案中存在的规则的答案,该规则非常接近官方C ++ 14标准。

原始答案的文字仍然包括在内,作为记录。

更新答案

根据NathanOliver的评论,gcc和clang在这种情况下会产生不同的输出:

g++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor
initializer list


clang++ -std=c++14 -Wall -pedantic -pthread main.cpp && ./a.out
default ctor
copy ctor
copy ctor

gcc是对的。

n4140 [dcl.init.list] / 1

  

列表初始化是从braced-init-list初始化对象或引用。

你在那里使用列表初始化,并且因为c是一个对象,所以列表初始化的规则在[dcl.init.list] / 3中定义:

[dcl.init.list] / 3:

  

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

     
      
  1. 如果T是汇总......
  2.   
  3. 否则,如果初始化列表没有元素......
  4.   
  5. 否则,如果Tstd::initializer_list<E> ...
  6. 的特化   

到目前为止列表:

  1. Foo不是聚合。
  2. 它有一个元素。
  3. Foo不是std::initializer_list<E>
  4. 的专精

    然后我们点击了[dcl.init.list] /3.4:

      

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

    现在我们到了某个地方。 13.3.1.7也称为[over.match.list]:

      

    按列表初始化初始化
      当非聚合类类型T的对象被列表初始化(8.5.4)时,重载决策分两个阶段选择构造函数:

         
        
    1. 最初,候选函数是类T的初始化列表构造函数(8.5.4),参数列表由初始化列表作为单个参数组成。
    2.   
    3. 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类T的所有构造函数,参数列表由初始化列表的元素组成。
    4.   

    因此,在重载决策的第二阶段,复制构造函数将仅在初始化列表构造函数之后被视为。应该在这里使用初始化列表构造函数。

    值得注意的是[over.match.list]然后继续:

      

    如果初始化列表没有元素且T具有默认构造函数,则省略第一个阶段。在复制列表初始化中,如果选择了显式构造函数,则初始化是不正确的。

    以及[dcl.init.list] / 3之后。 5 处理单元素列表初始化:

      

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

    解释了cppreference在单元素列表初始化中得到特殊情况的地方,尽管它们按顺序放置得比它应该更高。

    原始答案

    您遇到了列表初始化的一个有趣方面,如果列表满足某些要求,则可以将其视为复制初始化而不是列表初始化。

    来自cppreference

      

    类型T的对象的列表初始化的效果是:

         

    如果T是类类型,并且初始化列表具有单个元素   对象是相同或派生类型(可能是cv限定的)   从该元素初始化(通过复制初始化)   copy-list-initialization,或者直接初始化   直接列表初始化)。 (自c ++ 14起)

    Foo c{b}满足所有这些要求。

答案 1 :(得分:6)

让我们来看看C ++ 14规范中关于列表初始化的内容。 [dcl.init.list] 3有一系列规则,按顺序应用:

3.1不适用,因为Foo不是聚合。

3.2不适用,因为列表不为空。

3.3不适用,因为Foo不是initializer_list的专业化。

3.4确实适用,因为Foo是类类型。它说要考虑具有重载分辨率的构造函数,符合[over.match.list]。该规则首先要检查initializer_list构造函数 。由于您的类型具有initilaizer_list构造函数,因此编译器必须检查以查看是否可以根据给定值制造与这些构造函数匹配的initializer_list。它可以,因此 必须被称为

简而言之,GCC是对的,Clang 是错误的

应该注意的是,C ++ 17工作草案改变了 nothing 。它有一个新的3.1节,对单值列表有特殊的措辞,但仅适用于聚合Foo不是聚合,因此不适用。