74

在这里阅读一些关于转换运算符和构造函数的问题让我想到了它们之间的交互,即当有一个“模棱两可”的调用时。考虑以下代码:

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,编译器会很高兴地转而使用构造函数(代码没有其他更改)。

我的问题是:

  1. 由于编译器不认为B b = A();这是一个模棱两可的调用,所以这里必须有某种优先级。这个优先级究竟是在哪里建立的?(来自 C++ 标准的参考/引用将不胜感激)
  2. 从面向对象的哲学角度来看,这是代码应该表现的方式吗?谁知道更多关于一个A对象应该如何成为一个B对象,A或者B?根据 C++,答案是A——在面向对象的实践中是否有任何东西表明应该是这种情况?就我个人而言,无论哪种方式都有意义,所以我很想知道是如何做出选择的。

提前致谢

4

2 回答 2

56

您进行复制初始化,并且考虑在转换序列中进行转换的候选函数是转换函数和转换构造函数。这些是你的情况

B(const A&)
operator B() 

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

B(const A&)
B(A&)

第二个是因为转换函数是成员函数。A&是所谓的隐式对象参数,当候选者是成员函数时生成。现在,参数具有 type A。绑定隐式对象参数时,非常量引用可以绑定到右值。因此,另一条规则说,当您有两个参数为引用的可行函数时,具有最少const 资格的候选人将获胜。这就是您的转换功能获胜的原因。尝试制作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

重载解析用于选择要调用的用户定义转换。假设“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),并且引用所引用的类型除了顶级 cv 之外的类型相同-qualifiers,并且由 S2 初始化的引用所引用的类型比由 S1 初始化的引用所引用的类型更具有 cv 限定。
于 2009-09-05T19:05:22.580 回答
3

似乎 MSVS2008 对构造函数的选择有自己的看法:它在 B 中调用复制构造函数,而不管 A 的运算符的常量性如何。因此,即使标准指定了正确的行为,也要在这里小心。

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

A a;

B b = a;
于 2009-09-05T19:27:57.620 回答