【问题标题】:C++ virtual function not called in subclass子类中未调用 C++ 虚函数
【发布时间】:2011-01-07 11:55:46
【问题描述】:

考虑这个简单的情况:

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++ 规则是什么?

【问题讨论】:

  • 简单地说:B 不是 A! 它们彼此完全不相关,但是您的(不好用!)C 风格的演员表并不关心任何一种方式. dynamic_cast 将正确地遍历您的层次结构。当你转换不相关的指针类型时,你会得到未定义的行为。这意味着任何事情都可能发生,从看起来可以工作到炸毁您的计算机。
  • 别忘了鼻恶魔。

标签: c++ polymorphism virtual-functions


【解决方案1】:

您正在使用 C 样式转换无条件地将 b 转换为 A*。编译器不会阻止您这样做;你说它是A*,所以它是A*。所以它将它指向的内存视为A 的一个实例。由于a()A 的vtable 中列出的第一个方法,而b()B 的vtable 中列出的第一个方法,所以当您在一个实际上是B 的对象上调用a() 时,你会得到b()

你很幸运,对象布局是相似的。不保证是这样。

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

其次,你不应该依赖这种行为,除非你使用dynamic_cast&lt;&gt;

【讨论】:

  • 如果使用dynamic_cast,你可以完全依赖交叉转换。 100% 保证工作或退款。
  • @coppro:如果你使用 dynamic_cast,是的。我试图强调不要依赖这种使用 C 风格演员表的工作。我更新了我的上一条声明以使其更清楚。
【解决方案2】:

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

B* b = new C();
dynamic_cast<A*>(b)->a();

【讨论】:

  • 这应该是给定示例中的运行时错误。对吗?
  • @tehMick,不,这将是未定义的行为
  • 它进行运行时类型检查。它发现bC 的一个实例,它确实继承自A
  • 请注意,dynamic_cast 仅在您启用了运行时类型信息(Windows 编译器上的 /GR)时才有效。
  • @tehMick,dynamic_cast 知道对象类型是 C。由于 C 派生自 A,它能够进行转换,而无需运行时类型检查发现任何可抱怨的地方。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

      B* b = new C(); 
      A* a = dynamic_cast<A*>(b);
      assert(a != NULL);
      a->a();    
      

      【讨论】:

        【解决方案6】:

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

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

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

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

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

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

            【讨论】:

              猜你喜欢
              • 2010-09-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-04-02
              • 2021-05-18
              • 2011-09-08
              • 1970-01-01
              相关资源
              最近更新 更多