在我研究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{};
}
编译器不执行连续的用户定义转换
你的老师是对的。在您的代码示例中,这意味着当您指定时,Myclass
无法转换为Myclass1
:
Myclass2 m2 = Myclass{};
因为构造函数在创建Myclass1
时需要Myclass2
,并且编译器不能将Myclass
连续转换为Myclass1
然后用它来创建Myclass2
。但如果您有以下行:
Myclass1 m2 = Myclass{};
它会起作用,因为Myclass1
的构造函数将Myclass
作为参数。
更新:
您可能会问为什么这样做:
Myclass2 m2 {Myclass{}};
因为在这种情况下,构造函数被调用并且可以隐式地进行转换,除非您将Myclass1
声明为explicit
,这将导致代码编译失败(感谢Fureeish提醒),但是:
Myclass2 m2 = Myclass{};
就像调用需要引用的copy-constructor一样。所以如果你这样写,它会工作:
Myclass2 m2 = Myclass1(Myclass{});
正如EVG所提到的,如果未激活一致性模式(/ permissive-),则q 2017将接受Myclass2 m2 = Myclass{};
。
这条线
Myclass2 m2 = Myclass{};
表示复制初始化。引用cppreference.com:
如果
T
是一个类类型,并且other
类型的cv-unqualified版本不是T
或从T
[...]派生的,用户定义的转换序列可以从other
类型转换为T
[...检查并通过重载分辨率选择最佳的一个。
引用further:
用户定义的转换由零个或一个非显式单参数构造函数或非显式转换函数调用组成。
所以,Myclass2 m2 = Myclass{};
是不可接受的,因为它涉及两个用户定义的转换。
现在让我们来看看
Myclass2 m2 {Myclass{}};
在Afshin的回答中提出。这是一个直接初始化。规则是different:
检查
T
的构造函数,并通过重载决策选择最佳匹配。然后调用构造函数来初始化对象。
Myclass2
的构造函数接受Myclass1
,你需要一个用户定义的转换来从Myclass1
获得Myclass
。因此,它编译。
请注意,在VS复制中,如果未激活一致性模式(/premissive-
)(默认情况下),则将其视为直接启动。因此,VS接受Myclass2 m2 = Myclass{};
将其视为直接启动。有关示例,请参阅this document。
其他答案正在埋葬lede:你写的代码确实无效。 MSVC默认接受它,但MSVC这样做是错误的。您可以使用命令行开关/permissive-
强制MSVC更严格。 (你应该使用那个开关。)
Other compilers (GCC, clang), reject it.
一旦您将副本初始化更改为直接初始化,所有编译器都会接受代码,如其他答案所示。