考虑这种简单的情况:
A.h
class A {
public:
virtual void a() = 0;
};
B.h
#include <iostream>
class B {
public:
virtual void b() {std::cout << "b()." << std::endl;};
};
C.h
#include "A.h"
#include "B.h"
class C : public B, public A {
public:
void a() {std::cout << "a() in C." << std::endl;};
};
int main() {
B* b = new C();
((A*) b)->a(); // Output: b().
A* a = new C();
a->a(); // Output:: a() in C.
return 0;
}
换句话说:
- A是一个纯粹的虚拟课程
- B是一个没有超类和一个非纯虚函数的类
- C是A和B的子类,并覆盖A的纯虚函数。
令我惊讶的是第一个输出,即
((A*) b)->a(); // Output: b().
虽然我在代码中调用了(),但是调用了b()。我的猜测是,它与变量b是指向类B的指针这一事实有关,而B类不是类A的子类。但运行时类型仍然是指向C实例的指针。
从Java的角度来看,解释这个问题的确切C ++规则是什么?奇怪的行为?
答案 0 :(得分:24)
您无条件使用 C风格的演员将b
投射到A*
。编译器不会阻止你这样做;你说它是A*
所以它是A*
。因此它将它指向的内存视为A
的实例。由于a()
是A
的vtable中列出的第一种方法,而b()
是B
的vtable中列出的第一种方法,当您致电a()
时一个真正为B
的对象,你得到b()
。
你很幸运,对象布局是相似的。这不能保证是这种情况。
首先,你不应该使用C风格的演员表。你应该使用更安全的C++ casting operators(尽管你仍然可以用脚射击自己,所以仔细阅读文档)。
其次,除非使用dynamic_cast<>
,否则不应依赖此类行为。
答案 1 :(得分:11)
在多重继承树中进行投射时,请勿使用C样式转换。如果您使用dynamic_cast
而获得预期结果:
B* b = new C();
dynamic_cast<A*>(b)->a();
答案 2 :(得分:5)
您从B *开始并将其投射到A *。由于这两者是无关的,你正在深入研究未定义行为的范围。
答案 3 :(得分:2)
((A*) b)
是一个显式的c样式转换,无论指向哪种类型,都允许使用它。但是,如果您尝试取消引用此指针,则它将是运行时错误或不可预测的行为。这是后者的一个例子。您观察到的输出绝不安全或无法保证。
答案 4 :(得分:2)
A
和B
通过继承彼此无关,这意味着指向B
的指针无法通过方式转换为指向A
的指针无论是向上还是向下倾斜。
由于A
和B
是C
的两个不同基础,因此您在此处尝试执行的操作称为交叉投射。 C ++语言中唯一可以执行交叉投射的演员是dynamic_cast
。这是你在这种情况下必须使用的,如果你真的需要它(是吗?)
B* b = new C();
A* a = dynamic_cast<A*>(b);
assert(a != NULL);
a->a();
答案 5 :(得分:1)
以下行是reinterpret_cast,它指向相同的内存,但“假装”它是一种不同的对象:
((A*) b)->a();
你真正想要的是一个dynamic_cast,它会检查b实际上是什么类型的对象,并调整内存中指向的位置:
dynamic_cast<A*>(b)->a()
正如jeffamaphone所提到的,这两个类的类似布局是导致错误函数被调用的原因。
答案 6 :(得分:1)
在C ++中几乎从来没有一个场合使用C风格的强制转换(或其C ++等效的reinterpret_cast&lt;&gt;)是合理的或必需的。每当你发现自己想要使用其中一个时,就会怀疑你的代码和/或你的设计。
答案 7 :(得分:0)
我认为从B*
到A*
的投射有一个微妙的错误,行为未定义。避免使用C风格的强制转换,而更喜欢C ++强制转换 - 在本例中为dynamic_cast
。由于编译器为数据类型和vtable条目布置存储的方式,您最终找到了不同函数的地址。