调用虚函数和非虚函数之间的区别?

时间:2012-01-08 09:18:51

标签: c++ virtual-functions function-calls

这实际上是一个面试问题,我无法弄清楚答案。谁知道这个? 您可以谈论任何差异,例如,推入堆栈的数据。

5 个答案:

答案 0 :(得分:24)

虽然虚拟主义/动态调度是严格实现定义的,但大多数(读取所有已知的)编译器都使用vptrvtable来实现它。

话虽如此,调用非虚函数和虚函数之间的区别是:

statically处解析非虚拟函数Compile-time,而dynamically处的虚拟函数已解析Run-time

为了实现这种灵活性,能够决定在运行时调用哪个函数, 在虚函数的情况下有一点开销。

需要执行的额外fetch来电,这是您使用动态调度支付的开销/价格。

如果是非虚函数,则调用序列为:

fetch-call

编译器需要fetch函数的地址,然后call它。

在虚函数的情况下,序列为:

fetch-fetch-call

编译器需要从fetch vptr this,然后fetch vptr call函数的地址,然后{{1}}这个功能。

这只是一个简化的解释,实际的序列可能比这复杂得多,但这是你真正需要知道的,一个并不真正需要知道实现的细节。

好读:

<强> Inheritance & Virtual Functions

答案 1 :(得分:5)

如果你有一个基类'Base'和派生类'Derived',你有一个函数'func()'在Base类中被定义为virtual。 Derived类会覆盖此函数。

假设您定义

       Base obj = new Derived();
       obj.func();

然后调用Derived类的'func'。如果'func()'在Base中没有被定义为虚拟,那么它将从'Base'类调用。这是函数调用对于虚函数和非虚函数的不同之处

答案 2 :(得分:3)

调用虚方法时,必须在虚函数表中查找要调用的函数。

答案 3 :(得分:3)

调用虚方法的开销很大。

Also this.

答案 4 :(得分:2)

静态解析

非虚拟成员函数。成员函数是在编译时静态绑定,基于对象的指针(或引用)的类型。

相比之下,虚拟成员函数在运行时动态绑定。如果类具有至少一个虚拟成员函数,则编译器在构造对象期间将隐藏指针放在称为 vptr(虚拟表地址)的对象中。

编译器为每个具有至少一个虚函数的类创建一个v表。虚拟表包含虚函数的地址。它可以是虚函数指针的数组或列表(取决于编译器) 在调度虚函数期间,运行时系统遵循对象的v指针(从类对象获取地址)到类的v表,然后将offset添加到基址(vptr)并调用函数。

上述技术的空间成本开销是标称的:每个对象一个额外的指针(但仅适用于需要进行动态绑定的对象),以及每个方法的额外指针(但仅限于用于虚拟方法)。 时间成本开销也相当标称:与普通函数调用相比,虚函数调用需要两次额外的提取(一次获取v指针的值,另一次获取地址)方法)。

非虚函数不会发生此运行时活动,因为编译器会根据指针类型在编译时专门解析非虚函数。

我采用了一个简单的例子来更好地理解非虚函数和绑定是如何发生的。虚函数以及虚函数机制的工作原理。

#include<iostream>
using namespace std;
class Base
{
        public:
                virtual void fun()
                {}
                virtual void fun1()
                {}

                void get()
                {
                        cout<<"Base::get"<<endl;
                }
                void get1()
                {
                        cout<<"Base::get1"<<endl;
                }
};

class Derived :public Base
{
        public:
                void fun()
                {
                }
                virtual void fun3(){}
                void get()
                {
                        cout<<"Derived::get"<<endl;
                }
                void get1()
                {
                        cout<<"Derived::get1"<<endl;
                }

};
int main()
{
    Base *obj = new Derived();
    obj->fun();
    obj->get();
}

如何为Base&amp;创建vtable派生类

生成汇编代码以便更好地理解。

$ g++ virtual.cpp -S -o virtual.s

我分别从virtual.s for Base和Derived类中获取了vtable的信息:

_ZTV4Base:
        .quad   _ZN4Base3funEv
        .quad   _ZN4Base4fun1Ev
_ZTV7Derived:
        .quad   _ZN7Derived3funEv
        .quad   _ZN4Base4fun1Ev
        .quad   _ZN7Derived4fun3Ev

你可以看到有趣的&amp; fun1只是Base类中的两个虚函数。基类的Vtable(_ZTV4Base)具有两个虚函数的条目。 Vtable没有非虚函数的输入。请不要混淆有趣的名字(ZN4Base3funEv)&amp; fun1(ZN4Base4fun1Ev),他们的名字被破坏了。

派生类vtable具有树条目

  1. fun(_ZN7Derived3funEv)覆盖功能
  2. fun1(_ZN4Base4fun1Ev)继承自Base类
  3. fun3(_ZN7Derived4fun3Ev)派生类中的新函数
  4. 非虚拟功能&amp;虚函数叫做?

    非虚拟功能

        Derived d1;
        d1.get();
    
        subq    $16, %rsp
        leaq    -16(%rbp), %rax
        movq    %rax, %rdi
        call    _ZN7DerivedC1Ev //call constructor
        leaq    -16(%rbp), %rax
        movq    %rax, %rdi
        call    _ZN7Derived3getEv //call get function
    

    简单地说,获取并调用get(绑定发生在编译时)

    非虚拟功能

    Base *obj = new Derived();
    obj->fun();
    pushq   %rbx
    subq    $24, %rsp
    movl    $8, %edi
    call    _Znwm   //call new to allocate memory 
    movq    %rax, %rbx
    movq    $0, (%rbx)
    movq    %rbx, %rdi
    call    _ZN7DerivedC1Ev //call constructor
    movq    %rbx, -24(%rbp)
    movq    -24(%rbp), %rax
    movq    (%rax), %rax
    movq    (%rax), %rax
    movq    -24(%rbp), %rdx
    movq    %rdx, %rdi
    call    *%rax //call fun
    

    获取vptr,添加函数offset,调用函数(绑定发生在运行时)

    64的汇编令大多数c ++程序员感到困惑,但如果有人想讨论那么欢迎