在研究explicit
关键字之前,我的老师说:“编译器不会执行连续的用户定义转换”。如果是真的,我的代码中是否有任何错误?还是我误解了我的老师?我正在VS2017中工作。
#include<iostream>
#include <string>
class Myclass {
public:
Myclass() {
std::cout << "Myclass" << std::endl;
}
};
class Myclass1 {
public:
Myclass1(Myclass m) {
std::cout << "Myclass1" << std::endl;
}
};
class Myclass2{
public:
Myclass2(Myclass1 m) {
std::cout << "Myclass2" << std::endl;
}
};
int main() {
Myclass2 m2 = Myclass{};
}
答案 0 :(得分:13)
编译器不执行连续的用户定义转换
您的老师是对的。在您的代码示例中,这意味着您在以下位置分配时,Myclass
无法转换为Myclass1
:
Myclass2 m2 = Myclass{};
因为构造函数在创建Myclass1
时期望Myclass2
,并且编译器无法连续将Myclass
转换为Myclass1
,然后将其用于创建Myclass2
。但是,如果您有以下一行:
Myclass1 m2 = Myclass{};
这将起作用,因为Myclass1
的构造函数将Myclass
作为参数。
更新:
您可能会问这为什么起作用:
Myclass2 m2 {Myclass{}};
因为在这种情况下,将调用构造函数,并且可以隐式完成转换,除非您将Myclass1
声明为explicit
,这将使代码编译失败(感谢Fureeish提醒),但是在:
Myclass2 m2 = Myclass{};
就像调用需要参考的复制构造函数。因此,如果您这样编写,它将起作用:
Myclass2 m2 = Myclass1(Myclass{});
如EVG所述,如果未激活一致性模式(/ permissive-),则Myclass2 m2 = Myclass{};
被VS 2017接受。
答案 1 :(得分:8)
行
Myclass2 m2 = Myclass{};
表示复制初始化。引用cppreference.com:
如果
T
是类类型,并且other
类型的cv不合格版本不是T
也不是从T
派生的,则为[...],检查可以从other
类型转换为T
[...]类型的用户定义的转换序列,并通过重载分辨率选择最佳的转换序列。
引用further:
用户定义的转换由零个或一个非显式单参数构造函数或非显式转换函数调用组成。
因此,Myclass2 m2 = Myclass{};
是不可接受的,因为它将涉及两个用户定义的转换。
现在让我们看一下
Myclass2 m2 {Myclass{}};
建议使用 Afshin 的答案。这是直接初始化。规则为different:
检查
T
的构造函数,并通过重载分辨率选择最佳匹配。然后调用构造函数以初始化对象。
Myclass2
的构造函数接受Myclass1
,并且您需要一次用户定义的转换才能从Myclass1
获得Myclass
。因此,它将进行编译。
请注意,如果未激活一致性模式(/premissive-
)(默认情况下),则在VS中,复制初始化与直接初始化相同。因此,VS接受Myclass2 m2 = Myclass{};
将其视为直接初始化。有关示例,请参见this document。
答案 2 :(得分:2)
其他答案掩盖了线索:您编写的代码确实无效。默认情况下,MSVC接受它,但是MSVC这样做是错误的。您可以通过使用命令行开关/permissive-
来强制MSVC更严格。 (您应该使用该开关。)
Other compilers (GCC, clang), reject it.
将副本初始化更改为直接初始化后,所有编译器都会接受该代码,如其他答案所示。