C++ 为什么我们说虚拟函数的调用不能在编译时确定?

C++ 为什么我们说虚拟函数的调用不能在编译时确定?,c++,C++,可能重复: 我们说C++中虚函数的调用是在运行时确定的,而不是在编译时。所以我想我不清楚编译时和运行时之间的区别。在我看来,一切都应该在编译时确定。。。谁能在这个问题上提供帮助?谢谢!通常,您不知道调用虚拟函数时将执行什么代码: struct Base { virtual void method() = 0; }; void foo(Base* p) { p->method(); // What code will be execute here? } 如果有多个

可能重复:


<>我们说C++中虚函数的调用是在运行时确定的,而不是在编译时。所以我想我不清楚编译时和运行时之间的区别。在我看来,一切都应该在编译时确定。。。谁能在这个问题上提供帮助?谢谢!

通常,您不知道调用虚拟函数时将执行什么代码:

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

void foo(Base* p)
{
    p->method();   // What code will be execute here?
}
如果有多个类派生自
Base
,将执行哪些代码
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();
}
结构基{virtual void foo(){std::cout标志){ 基本*ptr=标志?&b:&d; ptr->foo(); }否则{
简言之,std::cout与上述内容一致
编译时是源代码编译的时间运行时是编译代码执行的时间,这可能取决于您对程序的输入……,因此,根据您的输入,在运行时决定哪个对象引用将访问虚拟函数

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

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

如果
vf()

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

struct Derived : Base
{
  virtual void func()
  { std::cout << "Derived::func()" << std::endl; }
};
编译器无法决定是在
bp->func()
中调用基类的函数还是派生类的函数,因为它取决于来自用户的输入

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



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

在任何给定函数中,在编译时,无论函数的输入参数值如何,您只能推断函数在任何可能的运行中都是正确的。无论何时输入函数的数据可能改变其行为,您都无法在编译时推导出结果

例如:

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()
时,我们不知道实际将调用什么函数

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


实际上,虚拟函数调用是使用vtable来解决的,vtable是指向类中所有虚拟函数的指针数组。

在技术层面上(希望我能直截了当地了解事实),有一种称为vtable的东西是为实现多态性而构建的

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

是编译器构建了vtable,并且只有在需要时才构建它们(即,如果类或其基类包含
虚拟函数
),因此值得注意的是,并非所有类都构建了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在中被替换以引导调用到虚拟函数的正确版本。因此这里的
派生的
有一个vtable和一个指向表的虚拟指针。因此当b指向Derived实例它将使用Derived中的vpointer调用正确的实现

这是在运行时完成的,或者说查找是在运行时完成的,因为我们可以很容易地让另一个类扩展Base,并使
b
指向这个类(让我们称之为另一个派生的
)类
是另一个派生的
的vpointer将用于评估对
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();
    */
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() )
    */
...
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();
    */