8

由于虚拟表是在编译时创建的,那么为什么我们将其称为 c++ 中的运行时多态性呢?

4

8 回答 8

12

因为查找发生在运行时。

于 2012-12-19T14:53:47.647 回答
8

在典型的实现中,每个类都有一个虚拟表,它在编译时是已知的。

在运行时,类型指针BaseClass *可能指向类型为 的对象BaseClass,或者指向类型为 的对象的基类子对象DerivedClass,其中BaseClass是 的基类DerivedClass。这同样适用于引用。

在前一种情况下,会在 的 vtable 中查找虚拟调用BaseClass。在后一种情况下,虚拟调用会在 的 vtable 中查找DerivedClass。由于调用站点不“知道”调用哪个函数,直到调用在运行时实际执行,这称为动态或运行时多态性。

同样在一个典型的实现中,它找出要使用哪个 vtable 的方式是具有一个或多个虚函数的类型的对象包含一个“隐藏的”附加字段,该字段指向其完整类型的 vtable。那是为了简单的继承。多重继承和虚拟继承增加了复杂性,但原理相同,对象提供了指向应使用的任何 vtable 的指针。

将此与非虚拟调用进行比较,其中编译器不需要使用任何 vtable 或知道完整对象的类型。它根据指针或引用的类型选择函数。

于 2012-12-19T15:01:23.930 回答
3

虚拟表无关紧要。C++ 中的运行时多态性意味着:

struct B {
    virtual void f() { std::cout << "In B\n"; }
};

struct D1 : B {
    virtual void f() { std::cout << "In D1\n"; }
};

struct D2 : b {
    virtual void f() { std::cout << "In D2\n"; }
};

B *bp = new B;
bp->f();   // calls B::f
B *bp1 = new D1;
bp1->f();  // calls D1::f
B *bp2 = new D2;
bp2->f();  // calls D2::f

即使所有三个指针都有 type B*,调用的行为f()取决于指针指向的对象的运行时类型。

于 2012-12-19T15:02:16.523 回答
1

正如您所说,每个类的虚函数表(和其他多态信息)是在编译时生成的。

每个对象都包含一个指向其动态类型的正确表的指针。该指针在创建对象时在运行时初始化,并在运行时用于选择要调用的正确虚函数。这就是为什么它被称为运行时多态性。

于 2012-12-19T15:14:29.853 回答
1

虚拟表在编译期间创建,但在运行时使用。

于 2012-12-19T14:54:31.860 回答
1

虚拟表是C++ 和其他一些 OO 语言中类类型的运行时表示的一个元素。它用于虚拟方法调用的动态调度。换句话说,它是 C++ 动态多态特性的一个实现细节

构建此表的时间与调度方案无关,调度方案定义了多态性是静态的还是动态的。

于 2012-12-19T15:30:15.927 回答
0

为此,您必须简要了解什么是运行时多态性以及它是如何工作的。

如果您有这样的类层次结构:

class Animal
{
    ...
    virtual void sayHello() {
        cout << "hello" << endl;
    }
};

class Dog : public Animal
{
    ...
    /*virtual*/ void sayHello() {
        cout << "woof!" << endl;
    }
};

并调用Dog实例上的方法,您希望能够调用 的(虚拟)覆盖方法Dog,而不是Animal。我想你知道我们为什么要这样做,所以我不想进一步解释。

但是我们怎么能在运行时知道 anAnimal*实际上是 aDog*呢?问题是,我们不必知道是什么,我们只需要知道要调用哪些函数,或者更好地说,要调用哪个函数指针。虚函数的所有函数指针都存储在每个类的虚表中。您可以将其想象为根据实际类调用哪个代码来调用哪个虚函数的“指南” 。

这个虚拟表是在编译期间创建的(编译器将“指南”写入可执行文件),每个实例都指向一个可用的虚拟表。因此,如果您说它Dog *dog = new Dog指向 Dog 的虚拟表。类似的调用编译为对尚未指定的类中dog->sayHello()的虚拟函数的虚拟调用...sayHello

然后,在运行时, like 调用dog->sayHello()将首先查找存储在对象中的具体虚拟表(代码不知道它是 Dog,只知道它是 Animal)并找到方法的函数指针Dog::sayHello()

为了回答您的问题,这种机制称为运行时多态性,因为我们可以调用重载 this 指针的方法(这意味着它们重载了您调用它们的对象的类型),而决策是在运行时做出的。您可以调用对应的编译时多态性,其中编译器可以知道对象的具体类型,例如在Dog dog; dog.sayHello().

于 2012-12-19T15:01:21.467 回答
0

虽然 v-table 确实是在编译时创建的,但是当编译以下代码时,编译器不知道将调用哪个函数:

struct A {
    virtual void f() { cout << "A::f" << endl;}
};
struct B : public A {
    void f() { cout << "B::f" << endl;}
};
int main() {
    A* b = new B();
    b->f(); // prints "B::f", chosen at runtime
}

因此,尽管对象在编译时和运行时之间没有变化,但方法 B::f 仅在运行时被选择,因为编译器不知道对象的动态类型(这决定了调用哪个方法)。

于 2016-06-26T15:49:18.137 回答