0

可能重复:
如果虚拟表是在编译时创建的,那么为什么我们将其称为运行时多态性?

我们说C++中虚函数的调用是在运行时确定的,而不是在编译时确定的。所以我想我不清楚编译时间和运行时间之间的区别。在我粗略的想法中,一切都应该在编译时确定......谁可以帮助解决这个问题?谢谢!</p>

4

8 回答 8

8

看这个简化的例子:

struct Base
{
  virtual void func()
  { std::cout << "Base::func()" << std::endl; }
};

struct Derived : Base
{
  virtual void func()
  { std::cout << "Derived::func()" << std::endl; }
};

有一个带有虚函数的基类和一个覆盖它的派生类。现在这是我们的主程序:

int main()
{
  Base *bp = 0;

  std::string input;
  std::cin >> input;

  if (input == "base")
    bp = new Base;
  else
    bp = new Derived;

  /* The compiler cannot decide which
     function is called here: */
  bp->func();

  return 0;
}

编译器无法决定是bp->func()调用基类函数还是派生类函数,因为它取决于来自用户的输入。

这说明了编译时和运行时之间的区别:编译器在编译时将您的代码转换为机器代码,但用户输入仅在运行时可用。


(我的代码示例并不是完美的代码,比如我用虚函数声明了类,而没有声明虚析构函数。还有其他问题。这只是为了说明编译时和运行时的区别,并说明什么每次都是和不可能的。)

于 2013-01-02T13:06:22.380 回答
2
class C
{
public:
    virtual void f() {}
};

class D : public C
{
public:
    void f() {}
};

void fn(C * c)
{
    // Is C::f or D::f called here?
    c->f();
}
于 2013-01-02T13:04:27.600 回答
2

有调用的事实是在编译时确定的。被调用的成员函数只有在对象已知的情况下才能知道。例如在

Base* ptr = (rnd() % 2 ? new D1() : new D2());
ptr->vf();

如果是一个虚函数,你不知道vf()在编译时调用哪个(假设 D1 和 D2 都有自己的) 。vf()

于 2013-01-02T13:05:41.800 回答
2

在任何给定的函数中,在编译时,您只能推断出在函数的任何可能运行中为真的事情,而不管其输入参数的值如何。每当输入到函数的数据可能会改变其行为时,您就无法在编译时推断出结果。

例如:

class A
{ 
   virtual void virt() = 0;
};

class B : public A 
{
   virtual void virt() { /*some computation */};
};


class C : public A 
{
   virtual void virt() { /*some other computation */};
};


void f(A* a)
{
    a->virt();
}

在这里,当我们编译时f,无法知道 所指向的对象a是类型B还是C,或者是我们甚至不知道的其他派生类型(在这个编译单元中)。事实上,这可能取决于用户输入,并且可能因运行而异

因此,在编译时,我们不知道在键入时实际会调用什么函数a->virt()

然而,在runtime中,我们确实知道a实际指向的是什么,因此我们可以确定将调用哪个函数。

在实践中,虚函数调用是使用vtable解决的,它是指向类的所有虚函数的指针数组。

于 2013-01-02T13:17:51.753 回答
1
#include <iostream>

struct Base { virtual void foo() { std::cout << "base\n"; } };
struct Derived : Base { void foo() { std::cout << "derived\n"; } };

int main() {
    Base b;
    Derived d;
    bool flag;
    if (std::cin >> flag) {
        Base *ptr = flag ? &b : &d;
        ptr->foo();
    } else {
        std::cout << "error\n";
    }
}

我不知道您目前认为的“编译时间”是什么,但程序编译和链接都发生在程序运行之前,因此在用户提供输入之前。所以调用的目的地foo不可能在编译时确定,因为它取决于运行时的用户输入。

如果foo是一个非虚拟函数,那么Base::foo()无论 的值如何都会被调用flag,因此在这种情况下,目标将在编译时已知。

于 2013-01-02T13:05:15.227 回答
0

一般来说,你不知道调用虚函数时会执行什么代码:

struct Base
{
    virtual void method() = 0;
};

void foo(Base* p)
{
    p->method();   // What code will be execute here?
}

如果有多个类派生自Base,将执行什么代码?

于 2013-01-02T13:04:18.330 回答
0

与上述一起,简而言之, 编译时间是编译源代码的时间运行时间是执行编译代码的时间,这可能取决于您对程序的输入......,因此根据根据您的输入,在运行时决定哪个对象引用将访问虚函数。这里

于 2013-01-02T13:05:29.610 回答
0

在技​​术层面上(希望我的事实直截了当:S),有一种叫做vtable的东西是为了实现多态性而构建的。

基本上每个只能有一个vtable,因此一个类的任何实例都将共享同一个vtable,可以说, vtable对程序员是不可见的,它包含指向虚函数实现的指针。

构建vtable的是编译器,并且仅在需要时才构建它们(即,如果类或其基类包含virtual function. 所以值得注意的是,并非所有类都构建了vtable 。

示例时间:

class Base {
public:
    virtual void helloWorld();
}

class Derived : public Base {
public:
    void helloWorld();
}

int main(void) {
    Derived d;
    Base *b = &d;

    b->helloWorld(); // here is the magic...

    /* This call is actually translated to something like the line below,
        lets assume we know that the virtual pointer pointing to the viable 
        for Derived is called Derived_vpointer (but it's only a name and 
        probably not what it would be called):

        *(b -> Derived_vpointer -> helloWorld() )
    */

因此,这意味着当b->helloWorld()被调用时,它实际上使用一个 vpointer 来查找一个 vtable,该 vtable 被替换以引导调用到正确版本的虚函数。所以这里的 Derived,有一个vtable和一个指向表的虚拟指针。因此,当b指向Derived实例时,它将使用 Derived 中的vpointer最终调用正确的实现。

这是在运行时完成的,或者更确切地说,查找是在运行时完成的,因为我们可以轻松地让另一个类扩展Baseb指向这个(让我们称之为AnotherDerived)类。当我们再次使用时会发生什么b->helloWorld()vpointerAnotherDerived用于评估对helloWorld().

所以让我们在代码中得到它..

...
int main(void) {
    Derived derived;
    AnotherDerived anotherDerived;
    Base *base;

    base->helloWorld(); 
    /* base points to a Base object, i.e. helloWorld() will be called for base. */

    *base = &derived; // base's vpointer will point at the vtable of Derived!
    base->helloWorld();
    /* calling:
        base->Derived_vpointer->helloWorld();
    */

    *base = &anotherDerived;
    base->helloWorld(); // base's vpointer will point at the vtable of AnotherDerivedClass
    /* calling:
        base->AnotherDerived_vpointer->helloWorld();
    */
于 2013-01-02T13:48:22.027 回答