转换构造函数与转换运算符: 优先级

阅读一些关于转换操作符和构造函数的问题,让我思考它们之间的相互作用,也就是当有一个“模糊的”调用时。考虑以下代码:

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 的转换运算符”,这意味着调用转换运算符与构造函数相对。如果从 A中删除/注释掉 operator B()代码,编译器将很高兴地切换到使用构造函数(代码没有其他更改)。

我的问题是:

  1. 由于编译器不认为 B b = A();是一个模棱两可的调用,因此这里必须有某种类型的优先级。这种优先权究竟是在哪里确立的?(请提供 C + + 标准的参考/引用)
  2. 从面向对象的哲学观点来看,这是代码应该采取的行为方式吗?谁知道更多关于 A对象应该如何成为 B对象,AB?根据 C + + ,答案是 A——在面向对象的实践中,是否有任何东西表明应该这样做?对我个人来说,这两种方式都有意义,所以我很想知道这个选择是如何做出的。

先谢谢你

15831 次浏览

您确实进行了复制初始化,并且被认为在转换序列中进行转换的候选函数是转换函数和转换构造函数。这些在你的箱子里

B(const A&)
operator B()

现在,这就是你声明他们的方式。重载解析从中抽象出来,并将每个候选项转换为与调用的参数对应的参数列表。参数是

B(const A&)
B(A&)

第二个是因为转换函数是一个成员函数。A&是所谓的隐式对象参数,当候选对象是成员函数时生成该参数。现在,参数的类型为 A。当绑定隐式对象参数时,非常量引用 可以绑定到一个 rvalue。因此,另一条规则是,当你有两个可行的函数,它们的参数是引用,那么具有 最少常量资格的候选者将获胜。这就是为什么你的转换函数赢了。尝试使 operator B成为一个 const 成员函数。你会注意到一个模棱两可的地方。

从面向对象的哲学观点来看,这是代码应该采取的行为方式吗?谁更了解一个 A 对象应该如何变成一个 B 对象,A 还是 B?根据 C + + ,答案是 A ——在面向对象的实践中,是否有任何东西表明应该是这种情况?对我个人来说,这两种方式都有意义,所以我很想知道这个选择是如何做出的。

郑重声明,如果将转换函数设置为 const 成员函数,那么 GCC 将选择构造函数(因此 GCC 似乎认为 B与它有更多的业务往来?).切换到迂腐模式(-pedantic) ,使其引起诊断。


标准码 8.5/14

否则(例如,对于其余的拷贝初始化情况) ,可以从源类型转换为目标类型或(当使用转换函数时)转换为其派生类的用户定义转换序列将按照13.3.1.4中的描述进行枚举,并且通过重载分辨率(13.3)选择最佳的转换序列。

还有 13.3.1.4

重载解析用于选择要调用的用户定义转换。假设“ cv1T”是正在初始化的对象的类型,使用类类型 T,候选函数的选择如下:

  • T 的转换构造函数(12.3.1)是候选函数。
  • 当初始值设定项表达式的类型为“ cv S”时,将考虑 S 及其基类的转换函数。那些没有隐藏在 S 中并且产生一个类型,其 cv 未限定版本与 T 类型相同或者是其派生类的类型是候选函数。返回“ reference to X”的转换函数返回 X 类型的 lvalue,因此在选择候选函数的过程中被认为是产生 X。

在这两种情况下,参数列表都有一个参数,即初始值设定项表达式。[注意: 此参数将与构造函数的第一个参数和转换函数的隐式对象参数进行比较。]

还有 13.3.3.2/3

  • 如果[ ... ] S1和 S2是引用绑定(8.5.3) ,那么标准转换序列 S1比标准转换序列 S2是更好的转换序列,而且引用所引用的类型除了顶级 cv 限定符之外都是相同的类型,并且由 S2初始化的引用所引用的类型比由 S1初始化的引用所引用的类型更加 cv 限定。

看起来 MSvs2008对构造函数的选择有自己的观点: 它在 b 中调用复制建构子,而不考虑 a 的运算符的常数。因此,即使 Standard 指定了正确的行为,在这里也要小心。

我以为 MSVS 只是在转换操作符之前搜索合适的构造函数,但是后来发现,如果从 B 的构造函数中删除 const 单词,它就会开始调用 A 的操作符 B ()。可能它对临时用户有一些特殊的行为,因为下面的代码仍然调用 B 的构造函数:

A a;


B b = a;