C++强制运算符重载和多态性

C++ cast operator overloading and polymorhism

本文关键字:多态性 重载 运算符 C++      更新时间:2023-10-16

我对C++的这种行为感到困惑:

struct A {
   virtual void print() const { printf("an"); }
};
struct B : public A {
   virtual void print() const { printf("bn"); }
};
struct C {
   operator B() { return B(); }
};
void print(const A& a) {
   a.print();
}
int main() {
   C c;
   print(c);
}

那么,测试是,程序的输出是什么——a还是b?答案是a。但是为什么呢?

这里的问题是C++03标准中的一个bug/misfeature/漏洞,不同的编译器试图以不同的方式修复这个问题。(这个问题在C++11标准中不再存在。(

两个标准的第8.5.3/5节规定了如何初始化引用。这是C++03版本(列表编号是我的(:

对类型cv1 T1的引用由类型cv2 T2的表达式初始化如下:

  1. 如果初始值设定项表达式

    1. 是左值(但不是位字段(,并且"cv1 T1"是与"cv2 T2"兼容的参考,或者
    2. 具有类类型(即T2是类类型(,并且可以隐式转换为类型cv3 T3的左值,其中cv1 T1是与cv3 T3兼容的引用

    然后在第一种情况下引用直接绑定到初始值设定项表达式lvalue,在第二种情况下,引用绑定到转换的lvalue结果。

  2. 否则,引用应为非易失性常量类型(即cv1应为const(。

  3. 如果初始值设定项表达式是右值,T2是类类型,并且cv1 T1是与cv2 T2兼容的引用,则引用以以下方式之一绑定(选项由实现定义(:

    1. 引用绑定到由右值表示的对象(见3.10(或该对象中的子对象
    2. 创建一个类型为cv1 T2[sic]的临时对象,并调用一个构造函数将整个右值对象复制到临时对象中。引用绑定到临时对象或临时对象中的子对象

    将用于制作副本的构造函数应是可调用的,无论副本是否实际完成。

  4. 否则,将使用非引用副本初始化的规则(8.5(从初始值设定项表达式创建并初始化类型为cv1 T1的临时项。然后将引用绑定到临时项。

手头的问题涉及三种类型:

  • 要创建的引用的类型。标准(两个版本(将这种类型表示为T1。在这种情况下,它是struct A
  • 初始值设定项表达式的类型。标准将这种类型表示为T2。在这种情况下,初始化器表达式是变量c,因此T2struct C。请注意,由于struct Astruct C不兼容引用,因此无法将引用直接绑定到c。需要一个中间体
  • 中间体的类型。标准将这种类型表示为T3。在这种情况下,这是struct B。注意,将转换运算符C::operator B()应用于c将把左值c转换为右值

我标记为1.1和3的初始化被取消,因为struct Astruct C不兼容。需要使用转换运算符C::operator B()。1.2 out因为这个转换运算符返回一个右值,所以这个规则1.2 out。剩下的就是选项4,创建一个类型为cv1 T1的临时。严格遵守2003年版本的标准迫使为这个问题设立两个临时机构,尽管只有一个就足够了。

2011年版本的标准通过将选项3替换为来解决问题

  • 如果初始值设定项表达式

    • 是xvalue、类prvalue、数组prvalue或函数lvalue,cv1 T1是引用-与cv2 T2兼容,或
    • 具有类类型(即,T2是类类型(,其中T1不是与T2相关的引用,并且可以隐式地转换为类型为cv3 T3的xvalue、类prvalue或函数lvalue,其中,cv1 T1是与cv3 T3兼容的引用

    则引用在第一种情况下绑定到初始值设定项表达式的值,在第二种情况下则绑定到转换的结果(或者,在任何一种情况下,绑定到适当的基类子对象(。在第二种情况下,如果引用是右值引用,并且用户定义的转换序列的第二个标准转换序列包括左值到右值的转换,则程序格式错误。

似乎gcc编译器家族选择了严格遵守而非意图(避免创建不必要的临时性(,而其他打印"b"的编译器则选择了对标准的意图/更正。选择严格遵守并不一定值得称赞;在2003版本的标准(例如std::set(中,gcc家族选择了理智而不是严格遵守。