在这里阅读有关转换运算符和构造函数的一些问题让我思考它们之间的相互作用,即当存在“模糊”调用时。请考虑以下代码:
class A;
class B {
public:
B(){}
B(const A&) //conversion constructor
{
cout << "called B's conversion constructor" << endl;
}
};
class A {
public:
operator B() //conversion operator
{
cout << "called A's conversion operator" << endl;
return B();
}
};
int main()
{
B b = A(); //what should be called here? apparently, A::operator B()
return 0;
}
上面的代码显示“被称为A的转换运算符”,这意味着调用转换运算符而不是构造函数。如果从operator B()
中删除/注释掉A
代码,编译器将很乐意切换到使用构造函数(不对代码进行其他更改)。
我的问题是:
B b = A();
是一个模糊的调用,因此这里必须有某种类型的优先级。这个优先权究竟在哪里确定? (将赞赏C ++标准的参考/引用)A
对象应如何成为B
对象,A
或B
?根据C ++,答案是A
- 在面向对象的实践中有什么表明应该是这种情况吗?就我个人来说,无论哪种方式都有意义,所以我很想知道如何做出选择。提前致谢
答案 0 :(得分:48)
您进行复制初始化,并且在转换序列中被视为进行转换的候选函数是转换函数和转换构造函数。这些是你的情况
B(const A&)
operator B()
现在,这就是你声明它们的方式。重载分辨率从中抽象出来,并将每个候选变换为与调用参数对应的参数列表。参数是
B(const A&)
B(A&)
第二个是因为转换函数是成员函数。 A&
是候选者是成员函数时生成的所谓隐式对象参数。现在,参数的类型为A
。绑定隐式对象参数时,非const引用可以绑定到右值。所以,另一条规则说当你有两个参数为引用的可行函数时,具有最少 const限定条件的候选者将获胜。这就是你的转换功能获胜的原因。尝试使operator B
成为const成员函数。你会发现一个含糊不清的地方。
从面向对象的哲学角度来看,这是代码应该表现的方式吗?谁更了解A对象应该如何成为B对象,A或B?根据C ++,答案是A - 在面向对象的实践中有什么表明应该是这种情况吗?对我个人来说,无论哪种方式都有意义,所以我很想知道如何做出选择。
对于记录,如果你将转换函数作为const成员函数,那么GCC将选择构造函数(因此GCC似乎认为B
有更多的业务吗?)。切换到迂腐模式(-pedantic
)以使其进行诊断。
Standardese,8.5/14
否则(即,对于剩余的复制初始化情况),可以如上所述枚举可以从源类型转换为目标类型或(当使用转换函数时)到其派生类的用户定义的转换序列。在13.3.1.4中,通过重载决策(13.3)选择最好的一个。
13.3.1.4
重载分辨率用于选择要调用的用户定义转换。假设“cv1 T”是要初始化的对象的类型,使用T类类型,候选函数选择如下:
- T的转换构造函数(12.3.1)是候选函数。
- 当初始化表达式的类型是类类型“cv S”时,考虑S及其基类的转换函数。那些未隐藏在S中并且产生其cv非限定版本与T的类型相同或者是其派生类的类型的候选函数。返回“引用到X”的转换函数返回X类型的左值,因此被认为在此选择候选函数的过程中产生X.
在这两种情况下,参数列表都有一个参数,它是初始化表达式。 [注意:此参数将与构造函数的第一个参数和转换函数的隐式对象参数进行比较。 ]
13.3.3.2/3
- 标准转换序列S1是比标准转换序列S2更好的转换序列,如果S1和S2是引用绑定(8.5.3),并且引用所引用的类型是相同的类型,除了顶部-level cv-qualifiers,以及S2引用的引用引用的类型比S1引用的引用引用的类型更符合cv。
答案 1 :(得分:3)
似乎MSVS2008对构造函数选择有自己的看法:它调用B中的复制构造函数,而不管A的运算符的常量。所以即使标准指定了正确的行为,也要小心。
我认为MSVS只是在转换运算符之前搜索合适的构造函数,但后来发现如果从B的构造函数中删除const字,它就会开始调用A的运算符B()。可能它对临时工具有一些特殊的行为,因为下面的代码仍然调用B的构造函数:
A a;
B b = a;