C ++虚函数未在子类中调用

时间:2010-01-20 21:52:51

标签: c++ polymorphism virtual-functions

考虑这种简单的情况:

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 ++规则是什么?奇怪的行为?

8 个答案:

答案 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)

AB通过继承彼此无关,这意味着指向B的指针无法通过方式转换为指向A的指针无论是向上还是向下倾斜。

由于ABC的两个不同基础,因此您在此处尝试执行的操作称为交叉投射。 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条目布置存储的方式,您最终找到了不同函数的地址。