复制初始化表单 '= {}'
Copy initialization of the form '= {}'
给定以下情况:
#include <stdio.h>
class X;
class Y
{
public:
Y() { printf(" 1n"); } // 1
// operator X(); // 2
};
class X
{
public:
X(int) {}
X(const Y& rhs) { printf(" 3n"); } // 3
X(Y&& rhs) { printf(" 4n"); } // 4
};
// Y::operator X() { printf(" operator X() - 2n"); return X{2}; }
int main()
{
Y y{}; // Calls (1)
printf("jn");
X j{y}; // Calls (3)
printf("kn");
X k = {y}; // Calls (3)
printf("mn");
X m = y; // Calls (3)
printf("nn");
X n(y); // Calls (3)
return 0;
}
到目前为止,一切都很好。现在,如果我启用转换操作员Y::operator X()
,我会得到; -
X m = y; // Calls (2)
我的理解是发生这种情况,因为(2(比(3(和(3(和因此优先。对X
构造函数的调用
我的问题是,为什么X k = {y}
的定义不以相同的方式改变其行为?我知道= {}
从技术上讲是"复制初始化",但是在没有使用initializer_list
类型的构造函数的情况下,这是否还原为"复制初始化"行为?IE-与X m = y
我理解的孔在哪里?
我理解的孔在哪里?
tltldr;没有人理解初始化。
tldr;列表初始化更喜欢std::initializer_list<T>
构造函数,但并不是非列表限制化的。它只能回到考虑构造函数。非列表初始化将考虑转换函数,但后备却没有。
所有初始化规则都来自[dcl.init]。因此,让我们从第一原则开始。
[dcl.init]/17.1:
- 如果初始化器是(非父母化( braced-init list or = braced-init-list ,则对象或参考被列表initialialized。
第一个子弹点涵盖了任何列表限制。这将X x{y}
和X x = {y}
跳到[dcl.init.list]。我们会回到这一点。另一种情况更容易。让我们看一下X x = y
。我们直接调用:
[dcl.init]/17.6.3:
- 否则(即,对于剩余的副本初始化案例(,用户定义的转换序列可以从源类型转换为目标类型,或者(当使用转换功能时(转换为派生类别的类别,如所述在[over.match.copy]中,最好的一个是通过超负荷分辨率选择的。
[over.match.copy]中的候选人是:
T
的转换构造函数[在我们的情况下,X
]是候选功能。- 当初始化器表达式的类型是类型" cv
S
"时,考虑了S
及其基础类的非明确转换功能。在这两种情况下,参数列表都有一个参数,即初始化器表达式。
这为我们提供了候选人:
X(Y const &); // from the 1st bullet
Y::operator X(); // from the 2nd bullet
第二个相当于拥有X(Y& )
,因为转换函数未得到CV的标记。这使得与转换构造函数相比,CV合格的参考较少,因此它是首选的。请注意,C 17中没有X(X&& )
的调用。
现在让我们回到列表限制案例。第一个相关的子弹点是[dcl.init.list]/3.6:
否则,如果
T
是类型,则考虑构造函数。列举了适用的构造函数,最好通过超载分辨率([over.match],[over.match.list](选择最好的构造函数。如果需要缩小转换(见下文(才能转换任何参数,则该程序的形式不佳。
在这两种情况下,我们都将[over.match.list]定义了两相超负荷分辨率:
- 最初,候选函数是T类的初始化列表构造函数([dcl.init.list](,参数列表由单个参数组成。
- 如果找不到可行的初始化列表构造函数,则再次执行过载分辨率,其中候选功能是T类的所有构造函数,而参数列表由初始器列表的元素组成。
如果初始化器列表没有元素,并且t具有默认构造函数,则省略了第一阶段。在副本列表初始化中,如果选择了显式构造函数,则初始化是不形成的。
候选人是X
的构造函数。X x{y}
和X x = {y}
之间的唯一区别在于,如果后者选择了explicit
构造函数,则初始化是不形成的。我们甚至没有任何explicit
构造函数,因此两个是等效的。因此,我们列举了我们的构造函数:
-
X(Y const& )
-
X(X&& )
通过Y::operator X()
前者是直接参考绑定,是确切的匹配。后者需要用户定义的转换。因此,在这种情况下,我们更喜欢X(Y const& )
。
请注意,GCC 7.1在C 1Z模式下犯错了,所以我已提交错误80943。
我的问题是,为什么定义x k = {y}不以相同的方式改变其行为?
因为,从概念上讲,= { .. }
是初始化,对于自动选择从括号中初始化目标初始化目标的"最佳"方法的东西,而= value
也是一个初始化,但从概念上讲也将 value 转换为不同的值。转换是完全对称的:是否会查看源值以查看它是否提供了创建目标的方法,并将查看目标以查看是否提供了接受源的方法。
如果您的目标类型为struct A { int x; }
,则使用= { 10 }
不会尝试将10
转换为A
(将失败(。但是它将寻求最佳(在他们眼中(初始化的形式,这在这里构成了汇总的初始化。但是,如果A
不是聚合(添加构造函数(,则将调用构造函数,在您的情况下,它可以轻松地发现Y
无需转换就可以接受。源和目标之间没有这样的对称性,就像使用= value
表格时的转换一样。
您对转换函数的"少量"的怀疑是完全正确的。如果将转换函数作为const成员,则将变得模棱两可。
- 是否可以初始化不可复制类型的成员变量(或基类)
- 复制列表初始化的隐式转换的等级是多少
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 犰狳C++ - 从常量内存初始化只读矩阵而不复制
- 可以通过复制来初始化集合吗?
- 正在复制具有未初始化成员的结构
- 直接初始化不可复制、不可移动的成员,而不使用聚合初始化
- 聚合初始化和删除的复制构造函数,也称为不可复制的 obejcts 作为字段
- 在引用初始化中使用已删除的复制构造函数进行复制初始化
- 为什么 std::string s = "123" 当不涉及副本时被视为复制初始化?
- 如何在向量列表初始化时避免对象复制以及如何延长临时的生存期
- 不可复制基类的聚合初始化
- 使用复制构造函数初始化 new[]
- 复制(显式)初始化与直接(隐式)初始化C++
- 在c++中的复制构造函数/一个声明语句中的初始化的延续中使用chain方法
- 不可复制类数据成员的统一初始化导致gcc错误
- 如何正确初始化复制构造函数(以类为引用的构造函数)
- C++初始化复制构造函数中的列表赋值,并在复制构造函数中崩溃
- 无法初始化复制构造函数中的数组
- 无法使用统一初始化复制 std::vector<std::function<void ()>>。这是对的吗?