Разница между вызовом виртуальной функции и виртуальные функции?

Это на самом деле вопрос интервью, я не могу понять ответ. Кто-нибудь знает об этом? Вы можете говорить о любой разнице, например, данные, которые толкают в стек.

5 ответов


хотя virtualism/динамическая диспетчеризация выполнения строго определен, большинство(читай все известные) компиляторы реализуют его с помощью vptr и vtable.

сказав это, разница между вызовом не виртуальной функции и виртуальной функции:

разрешены не виртуальные функции statically at Compile-time, в то время как виртуальные функции решаются dynamically at Run-time.

для того, чтобы достичь этого гибкость возможности решать, какую функцию вызывать во время выполнения, существует немного накладных расходов в случае виртуальных функций.

дополнительно fetch вызов, который необходимо выполнить, и это накладные расходы/цена, которую вы платите за использование динамической отправки.

в случае не-виртуальной функции, последовательность вызовов:

fetch-call

компилятор должен fetch адрес функции, а затем call его.

в то время как в случае виртуальные функции последовательность:

fetch-fetch-call

компилятор должен fetch на vptr С this, потом fetch адрес функции из vptr а то call функции.

это просто упрощенное объяснение фактическая последовательность может быть намного сложнее, чем это, но это то, что вам действительно нужно знать, на самом деле не нужно знать реализацию nitty gritty.

Хорошее Чтение:

Наследование И Виртуальные Функции


Если у вас есть базовый класс "Base" и производный класс "Derived", и у вас есть функция " func ()", определенная как виртуальная в базовом классе. Эта функция переопределяется в производном классе.

предположим, что вы определили

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

затем вызывается "func" производного класса. Хотя, если "func ()" не был определен как виртуальный в базе, он будет вызываться из "базового" класса. Это разница в том, как вызов функции отличается для vitual и non-virtual функций


при вызове виртуального метода, он должен выглядеть, какие функции в таблице виртуальных функций.


накладные расходы на вызов виртуального метода значительны.

и этого.


не виртуальные функции-члены статически решен. функция-членсвязывание статически во время компиляции на основе типа указателя (или ссылки) на объект.

В противоположность виртуальные функции-члены are динамическое связывание во время выполнения. Если класс имеет по крайней мере одну виртуальную функцию-член, то компилятор помещает скрытый указатель в объект, называемый vptr (виртуальный адрес таблицы) во время строительства объекта.

на компилятор создает v-таблицу для каждого класса, имеющий по крайней мере одну виртуальную функцию. Виртуальная таблица содержит адрес виртуальной функции. Это может быть массив или список (в зависимости от компилятора) указателя виртуальной функции
Во время отправки виртуальной функции Система времени выполнения следует за v-указателем объекта(извлекает адрес из объекта класса) в v-таблицу класса, затем смещение добавляется к базовому адресу (vptr) и вызов функции.

на пространство-стоимостью накладные расходы вышеуказанного метода номинальны: дополнительный указатель на объект (но только для объектов, которые должны будут выполнять динамическую привязку), плюс дополнительный указатель на метод (но только для виртуальных методов). The время-стоимость накладные расходы также довольно номинальны: по сравнению с обычным вызовом функции вызов виртуальной функции требует двух дополнительных выборок (один для получения значения 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();
}

как vtable создан для Base & derived класс!--37-->

ассемблерный код генерируется для лучшего понимания.

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

я получил информацию о vtable из virtual.s Для базового и производного классов соответственно:

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

как вы можете видеть, fun & fun1-это только две виртуальные функции в базовом классе. Vtable базового класса (_ZTV4Base) имеют записи обеих виртуальных функций. Vtable не имеет записи не виртуальной функции. Пожалуйста, не путайте с именем fun(ZN4Base3funEv) & fun1 (ZN4Base4fun1Ev), их имя было искажено.

производный класс vtable имеет записи дерева

  1. fun (_ZN7Derived3funEv) переопределить функцию
  2. fun1 (_ZN4Base4fun1Ev) наследуется от базового класса
  3. fun3 (_ZN7Derived4fun3Ev) новая функция в производном классе

как виртуальные функции и виртуальные функции?

для non-virtual функция

    Derived d1;
    d1.get();

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

просто скажите, fetch и вызовите get (привязка произошла во время компиляции)

для не-виртуальной функции

Base *obj = new Derived();
obj->fun();
pushq   %rbx
subq    , %rsp
movl    , %edi
call    _Znwm   //call new to allocate memory 
movq    %rax, %rbx
movq    , (%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

fetch vptr, добавьте смещение функции, вызовите функцию (привязка произошла во время выполнения)

сборка 64 сбивает с толку большинство программистов на c++, но если кто-то хотел бы обсудить, то добро пожаловать