7

考虑这个简单的情况:

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().

虽然我在代码中调用了 a(),但调用了 b()。我的猜测是,这与变量 b 是指向 B 类的指针有关,而 B 类不是 A 类的子类。但运行时类型仍然是指向 C 实例的指针。

从 Java 的角度来看,解释这个奇怪行为的确切 C++ 规则是什么?

4

8 回答 8

24

您正在无条件地转换bA*使用C 风格的 cast。编译器不会阻止您这样做;你说它是一个A*所以它是一个A*。所以它把它指向的内存当作A. 由于a()是 的 vtable 中列出的第一个方法,A并且b()是 的 vtable 中列出的第一个方法B,所以当您调用a()一个真正是 a 的对象时B,您会得到b().

你很幸运,对象布局是相似的。不保证会出现这种情况。

首先,您不应该使用 C 风格的强制转换。您应该使用更安全的C++ 强制转换运算符(尽管您仍然可以在脚上开枪,因此请仔细阅读文档)。

其次,你不应该依赖这种行为,除非你使用dynamic_cast<>.

于 2010-01-20T21:59:14.777 回答
11

跨多继承树进行强制转换时,不要使用 C 风格的强制转换。如果你dynamic_cast改用你会得到预期的结果:

B* b = new C();
dynamic_cast<A*>(b)->a();
于 2010-01-20T22:00:13.280 回答
5

您从 B* 开始并将其转换为 A*。由于这两者是不相关的,因此您正在深入研究未定义行为的领域。

于 2010-01-20T22:00:33.857 回答
2

((A*) b)是一个显式的 c 风格转换,无论指向什么类型都是允许的。但是,如果您尝试取消引用此指针,它将是运行时错误或不可预测的行为。这是后者的一个例子。您观察到的输出绝不是安全或有保证的。

于 2010-01-20T22:00:20.270 回答
2

A并且B没有通过继承的方式相互关联,这意味着指针B不能通过A向上转换或向下转换的方式转换为指针。

由于AB是 的两个不同基数C,因此您在此处尝试执行的操作称为交叉转换。C++ 语言中唯一可以执行交叉转换的转换是dynamic_cast. 这是你在这种情况下必须使用的,以防你真的需要它(是吗?)

B* b = new C(); 
A* a = dynamic_cast<A*>(b);
assert(a != NULL);
a->a();    
于 2010-01-20T22:19:26.447 回答
1

以下行是一个 reinterpret_cast,它指向同一个内存,但“假装”它是一种不同的对象:

((A*) b)->a();

你真正想要的是一个 dynamic_cast,它检查 b 到底是什么类型的对象并调整内存中指向的位置:

dynamic_cast<A*>(b)->a()

正如 jeffamaphone 所提到的,这两个类的相似布局是导致调用错误函数的原因。

于 2010-01-20T22:04:26.813 回答
1

在 C++ 中,几乎从来没有一个场合需要使用 C 风格的强制转换(或其 C++ 等效的 reinterpret_cast<>)。每当您发现自己想使用两者之一时,请怀疑您的代码和/或您的设计。

于 2010-01-20T22:08:23.587 回答
0

我认为你在从B*to转换时有一个微妙的错误A*,并且行为是未定义的。避免使用 C 风格的强制转换并更喜欢 C++ 强制转换 - 在这种情况下dynamic_cast。由于您的编译器为数据类型和 vtable 条目布置存储的方式,您最终找到了不同函数的地址。

于 2010-01-20T22:02:53.370 回答