我对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?嗯,答案是答案。但为什么呢?
答案 0 :(得分:10)
这里的问题是C ++ 03标准中的错误/错误/漏洞,不同的编译器试图以不同的方式修补问题。 (C ++ 11标准中不再存在此问题。)
两个标准的第8.5.3 / 5节都规定了如何初始化参考。这是C ++ 03版本(列表编号是我的):
类型
cv1 T1
的引用由cv2 T2
类型的表达式初始化,如下所示:
如果是初始化表达式
- 是左值(但不是位字段),“cv1 T1”与“cv2 T2”或
引用兼容- 具有类类型(即
醇>T2
是类类型),可以隐式转换为cv3 T3
类型的左值,其中cv1 T1
与{{1}引用兼容}}然后引用在第一种情况下直接绑定到初始化表达式lvalue,并且引用在第二种情况下绑定到转换的左值结果。
否则,引用应为非易失性const类型(即
cv3 T3
应为cv1
)。如果初始化表达式是右值,
const
类类型,T2
与cv1 T1
引用兼容,则引用绑定在以下之一方式(选择是实现定义的):
- 引用绑定到rvalue(参见3.10)表示的对象或该对象中的子对象。
- 创建类型
醇>cv2 T2
[sic]的临时表,并调用构造函数将整个右值对象复制到临时对象中。引用绑定到临时或临时的子对象。无论副本是否真正完成,用于制作副本的构造函数都应该是可调用的。
- 醇>
否则,将使用非引用副本初始化(8.5)的规则从初始化表达式创建并初始化类型
cv1 T2
的临时表。然后将引用绑定到临时。
手头的问题涉及三种类型:
cv1 T1
。在这种情况下,它是T1
。struct A
。在这种情况下,初始化表达式是变量T2
,因此c
是T2
。请注意,由于struct C
与struct A
不是参考兼容,因此无法直接将引用绑定到struct C
。需要一个中间体。c
。在这种情况下,这是T3
。请注意,将转化运算符struct B
应用于C::operator B()
会将左值c
转换为右值。我标记为1.1和3的初始化已经完成,因为c
与struct A
不是参考兼容的。需要使用转换运算符struct C
。 1.2是因为这个转换运算符返回一个rvalue,这规则为1.2。剩下的就是选项4,创建一个C::operator B()
类型的临时表。严格遵守2003版本的标准迫使为此问题创建两个临时工,即使只有一个就足够了。
2011版标准通过将选项3替换为
来解决问题
如果是初始化表达式
- 是xvalue,类prvalue,数组prvalue或函数lvalue,
兼容cv1 T1
是引用 - 与cv1 T1
或- 具有类类型(即,
cv2 T2
是类类型),其中T2
与T1
不是引用相关的,并且可以隐式转换为xvalue,类prvalue ,或函数左值为T2
,其中cv3 T3
与cv1 T1
引用兼容,然后引用绑定到第一种情况下的初始化表达式的值,并绑定到第二种情况下的转换结果(或者,在任何一种情况下,绑定到适当的基类子对象)。在第二种情况下,如果引用是右值引用,并且用户定义的转换序列的第二个标准转换序列包括左值到右值转换,则程序格式不正确。
gcc系列编译器似乎选择严格遵守意图(避免创建不必要的临时),而打印“b”的其他编译器选择了标准的意图/更正。选择严格的合规并不一定值得称道;在2003版本的标准中存在其他错误/错误(例如,cv3 T3
),其中gcc家族选择了严格遵守的理智。