非虚方法调用虚拟方法,C ++

时间:2015-12-01 05:22:48

标签: c++

这是我正在谈论的代码

#include <stdio.h>
#include <stdlib.h>

class A {
public:
  void method() {printf("method A\n");}
  virtual void parentMethod() { printf("parentMethod\n"); method(); }
};

class B : public A {
public:
  void method() {printf("method B\n");}
};

int main(void) {
  A a;
  B b;

  a.parentMethod();
  b.parentMethod();
}

我的问题是为什么会这样?为什么在调用b.parentMethod()时它不会打印method B。我意识到它与method()在A和B中存在以及非虚拟有关,但我无法理解它。有人能够解释这种行为吗?

感谢任何帮助。

4 个答案:

答案 0 :(得分:2)

您的代码缺少virtual关键字:

#include <stdio.h>
#include <stdlib.h>

class A {
public:
  virtual void method() {printf("method A\n");} // virtual was missing
  void parentMethod() { printf("parentMethod\n"); method(); } // unnecessary virtual keyword
};

class B : public A {
public:
  void method() {printf("method B\n");}
};

int main(void) {
  A a;
  B b;

  a.parentMethod();
  b.parentMethod();
}

最上层的定义必须包含virtual关键字。如果你考虑它,这是非常合乎逻辑的。在这种情况下,当您调用method()时,编译器知道它必须立即执行比正常函数调用更多的操作。 否则,它必须查找并迭代所有派生类型,以查看它们是否包含method()的重新定义。

答案 1 :(得分:2)

  

我的问题是为什么会这样?为什么在调用b.parentMethod()时它不会打印方法B.我意识到它有一些东西要做   使用method()在A和B中以及非虚拟,但是我   我无法理解它。有人能够解释这个   行为?

C ++在类/结构方面有两个间接层次。你有“普通功能”(包括“重载”,“lambdas”,静态等),你有“虚拟功能”。首先,让我们解释一下“普通函数”。

struct Foo {
    void goo();
};

在这种结构中,goo只是一个普通的旧函数。如果您尝试用C语言编写,这类似于调用

void goo(struct Foo *this);

没什么神奇的,只是一个带有“隐藏”指针的普通函数(所有c ++函数都将“隐藏”这个指针传递给它们)。现在,让我们在继承的结构中重新实现这个功能,

struct Goo : public Foo {
    void goo();
};
...
Goo g;
g.goo();

Foo f;
f.goo();

此处,明确为日,g.goo()goo()结构中调用Goo,在Foo结构中调用f.goo()调用goo()。所以,在C函数中,这只是,

void goo(struct Foo *this);
void goo(struct Goo *this);

提供C进行参数重载。但仍然只是简单的功能。在Foo对象中调用goo()将调用与在Goo对象中调用goo()不同的函数。仅编译时间分辨率。但现在,让我们的功能“虚拟”

struct Foo {
    virtual void goo();
};

struct Goo : public Foo {
    void goo();  // <- also virtual because Foo::goo() is virtual
                 // In C++11 you'll want to write
                 //     void goo() override;
                 // which verifies that you spelled function name correctly
                 // and are not making *new* virtual functions! common error!!
};
...
Goo g;
g.goo();

Foo f;
f.goo();

这里发生的是Foo现在包含一个“虚拟表”。编译器现在创建一个映射“最新”goo()函数位置的函数表。也就是说,隐式Foo()构造函数会执行类似的操作,

virt_table[goo_function_idx] = &Foo::goo;

然后 Goo()中的构造函数将使用,

更新此表
virt_table[goo_function_idx] = &Goo::goo;

然后当你有,

Foo *f = new Goo();
f->goo();

发生的事情类似于,

f->virt_table[goo_function_idx]();

在“虚拟表”中​​查找函数位置,并调用该函数。这意味着函数的运行时解析或多态。这就是Goo :: goo()的调用方式。

如果没有此表,编译器只能调用它对所述对象知道的函数。因此,在您的示例中,b.parentMethod()在表中查找并调用。但是method()不是该表的一部分,因此只尝试编译时解析。由于this指针为A*,因此您会调用A::method

我希望这会清除“虚拟桌”业务 - 它实际上是一个内部查找表,但仅适用于标记为virtual的函数!

PS。您可能会问,“但是this指针会被虚拟表从Foo *”转换为Goo *“,是的,它会。我会留下它作为练习,让你弄清楚为什么总是正确的。

答案 2 :(得分:1)

virtual表示可以在子类中重写方法。我认为您希望method覆盖parentMethod,而不是B。我已将parentMethod重命名为foo,以减少误解。

#include <stdio.h>
#include <stdlib.h>

class A {
public:
  virtual void method() {printf("method A\n");}
  void foo() { printf("foo\n"); method(); }
};

class B : public A {
public:
  void method() {printf("method B\n");}
};

int main(void) {
  A a;
  B b;

  a.foo();
  b.foo();
}

你会看到这给出了预期的输出:

foo
method A
foo
method B

这是ideone

答案 3 :(得分:1)

你是对的。发生这种情况是因为您的method不是虚拟的。当您通过父类调用它时,它无法知道它已被重载,因此总是调用A::method。如果您将method标记为虚拟,则对其的调用将通过类vtable进行路由,因此A::method将被上升类中的B::method替换。