ged*_*ial 18 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;
}
Run Code Online (Sandbox Code Playgroud)
输出是:
默认ctor
复制ctor
复制ctor
在第三种情况下,我将b放入大括号初始化,它应该调用initializer_list <>构造函数.
相反,复制构造函数起带头作用.
你们有人会告诉我这是如何运作的,为什么?
jag*_*ire 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
Run Code Online (Sandbox Code Playgroud)
gcc是对的.
n4140 [dcl.init.list]/1
列表初始化是从braced-init-list初始化对象或引用.
你在那里使用列表初始化,因为它c
是一个对象,其列表初始化的规则在[dcl.init.list]/3中定义:
[dcl.init.list]/3:
列表初始化对象或类型T的引用定义如下:
- 如果
T
是聚合......- 否则,如果初始化列表没有元素......
- 否则,如果
T
是std::initializer_list<E>
... 的专业化
到目前为止列表:
Foo
不是聚合.Foo
不是专业化的std::initializer_list<E>
.然后我们点击[dcl.init.list] /3.4:
否则,如果
T
是类类型,则考虑构造函数.枚举适用的构造函数,并通过重载决策(13.3,13.3.1.7)选择最佳构造函数.如果转换任何参数需要缩小转换(见下文),则程序格式错误.
现在我们到了某个地方.13.3.1.7也称为[over.match.list]:
通过列表初始化初始化
当非聚合类类型的对象T
被列表初始化(8.5.4)时,重载决策分两个阶段选择构造函数:
- 最初,候选函数是类的初始化列表构造函数(8.5.4),
T
参数列表由初始化列表作为单个参数组成.- 如果找不到可行的初始化列表构造函数,则再次执行重载解析,其中候选函数是类的所有构造函数,
T
参数列表由初始化列表的元素组成.
因此,只有在初始化列表构造函数之后,才会在重载决策的第二阶段考虑复制构造函数.应该在这里使用初始化列表构造函数.
值得注意的是[over.match.list]然后继续:
如果初始化列表没有元素并且
T
具有默认构造函数,则省略第一个阶段.在复制列表初始化中,如果选择了显式构造函数,则初始化是错误的.
在[dcl.init.list]/3之后.5处理单元素列表初始化:
否则,如果初始化列表具有单个元素类型
E
且且T
不是引用类型或其引用类型与引用相关E
,则从该元素初始化对象或引用; 如果将元素转换为需要缩小转换(见下文)T
,则程序格式不正确.
这解释了cppreference在单元素列表初始化中获得特殊情况的地方,尽管它们按顺序放置得比它应该更高.
您遇到了列表初始化的一个有趣方面,如果列表满足某些要求,则可以将其视为复制初始化而不是列表初始化.
来自cppreference:
列表初始化类型对象的效果
T
是:如果
T
是类类型且初始化列表具有相同或派生类型的单个元素(可能是cv限定的),则从该元素初始化对象(通过复制初始化进行复制列表初始化,或通过直接初始化用于直接列表初始化).(自c ++ 14起)
Foo c{b}
满足所有这些要求.
让我们来看看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工作草案对此没有任何改变.它有一个新的3.1节,对单值列表有特殊的措辞,但这只适用于聚合.Foo
不是聚合,因此不适用.
归档时间: |
|
查看次数: |
723 次 |
最近记录: |