【问题标题】:Conversion constructor vs. conversion operator: precedence转换构造函数与转换运算符:优先级
【发布时间】:2010-11-25 22:07:24
【问题描述】:

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

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——在面向对象的实践中是否有任何东西表明应该是这种情况?就我个人而言,无论哪种方式都有意义,所以我很想知道是如何做出选择的。

提前致谢

【问题讨论】:

  • 你注释“//拷贝构造函数”的那行不是拷贝构造函数,它是构造函数。
  • 你说得对,我误用了这个词。我已经编辑了。

标签: c++ constructor operators type-conversion conversion-operator


【解决方案1】:

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

B(const A&)
operator B() 

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

B(const A&)
B(A&)

第二个是因为转换函数是成员函数。 A&amp; 是所谓的隐式对象参数,当候选者是成员函数时生成。现在,参数的类型为A。绑定隐式对象参数时,非常量引用可以绑定到右值。因此,另一条规则说,当您有两个参数是引用的可行函数时,具有 fewest 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 是引用绑定 (8.5.3),则标准转换序列 S1 是比标准转换序列 S2 更好的转换序列,并且引用所引用的类型除了 top 之外是相同的类型-level cv-qualifiers,并且由 S2 初始化的引用所引用的类型比由 S1 初始化的引用所引用的类型更具 cv 限定性。

【讨论】:

  • 啊,所以我的问题不在于构造函数和运算符之间的优先级,而是每个的const-ness。你是对的,将operator B() 更改为operator B() const 会导致歧义错误。
  • 我把同样的“分析”放在我的另一个答案上作为一个例子,真是巧合:stackoverflow.com/questions/1051379/…xD
  • 关于答案第一句话的快速问题 - you do copy initialization...。我没有看到任何地方出现副本-即,没有从另一个A构造A(也没有从另一个B构造B)-所以我说得对,你不是字面意思@987654342 @,而是类似于assignment initialization 的东西?谢谢!
  • 另一个关于转换构造函数B(const A&amp;) 的转换的问题是您在答案开头附近提到的重载解析抽象的结果:据我所知,所有构造函数也使用@987654345 @ 指针隐式,就像 operator B() 函数所做的那样(正如您突出显示的那样)。因此,对于靠近顶部的转换后的转换构造函数,写void(B&amp;,const A&amp;) 而不是B(const A&amp;) 不是正确的吗?请注意,您自己的答案之一似乎表明了这一点 - 请参阅stackoverflow.com/a/2483725/368896
  • +1 表示提到的-pedantic。没有它,我无法使用example code from cppreference.com 重现歧义。责备g++
【解决方案2】:

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

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

A a;

B b = a;

【讨论】:

    猜你喜欢
    • 2012-09-02
    • 2014-07-28
    • 2021-12-04
    • 1970-01-01
    • 2017-07-04
    • 2023-03-10
    • 2012-05-04
    • 2021-01-01
    • 1970-01-01
    相关资源
    最近更新 更多