22

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

struct A {
   virtual void print() const { printf("a\n"); }
};

struct B : public A {
   virtual void print() const { printf("b\n"); }
};

struct C {
   operator B() { return B(); }
};

void print(const A& a) {
   a.print();
}

int main() {
   C c;
   print(c);
}

所以,测验是,程序的输出是什么——a 还是 b?嗯,答案是一个。但为什么?

4

1 回答 1

10

这里的问题是 C++03 标准中的错误/错误功能/漏洞,不同的编译器试图以不同的方式修补问题。(这个问题在 C++11 标准中不再存在。)

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

对 type 的引用cv1 T1由 type 的表达式初始化,cv2 T2如下所示:

  1. 如果初始化表达式

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

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

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

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

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

    无论复制是否实际完成,用于制作复制的构造函数都应该是可调用的。

  4. cv1 T1否则,使用非引用复制初始化规则(8.5)从初始化表达式创建并初始化一个临时类型。然后将引用绑定到临时文件。

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

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

我标记为 1.1 和 3 的初始化已经失效,struct A因为struct C. C::operator B()需要使用转换运算符。1.2 out 因为这个转换运算符返回一个右值,所以这个规则 1.2 out。剩下的就是选项 4,创建一个临时类型的cv1 T1. 严格遵守 2003 版标准强制为这个问题创建两个临时的,即使只有一个就足够了。

2011 版标准通过将选项 3 替换为

  • 如果初始化表达式

    • 是一个 xvalue、类纯右值、数组纯右值或函数左值,并且与cv1 T1引用兼容cv2 T2,或
    • 具有类类型(即T2是类类型),其中T1与 不引用相关T2,并且可以隐式转换为类型的 xvalue、类纯右值或函数左值cv3 T3,其中cv1 T1与 引用兼容cv3 T3

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

似乎 gcc 系列编译器选择了严格的合规性而不是意图(避免创建不必要的临时变量),而其他打印“b”的编译器选择了对标准的意图/更正。选择严格合规不一定值得称道。在 2003 版标准(例如 )中还有其他错误/错误功能,std::set其中 gcc 家族选择理智而不是严格遵守。

于 2013-01-13T04:47:51.490 回答