Разница между вызовом виртуальной функции и не виртуальной функции? - PullRequest
9 голосов
/ 08 января 2012

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

Ответы [ 5 ]

25 голосов
/ 08 января 2012

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

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

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

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

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

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

fetch-call

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

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

fetch-fetch-call

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

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

Хорошо Читать:

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

5 голосов
/ 08 января 2012

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

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

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

Затем вызывается «функция» класса Derived. Если бы func () не был определен как виртуальный в Base, он был бы вызван из класса Base. В этом различие между вызовом функции для витальных и не виртуальных функций

3 голосов
/ 08 января 2012

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

Также это.

3 голосов
/ 08 января 2012

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

2 голосов
/ 25 декабря 2015

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

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

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

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

Как создается виртуальная таблица для базового и производного класса

Код ассемблера сгенерирован для лучшего понимания.

$ 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) override function
  2. fun1 (_ZN4Base4fun1Ev) унаследовано от Базового класса
  3. fun3 (_ZN7Derived4fun3Ev) новая функция в производном классе

Как не виртуальная функция и виртуальная функцияВызванный?

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

    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, добавить смещение функции, вызвать функцию (привязка произошла во время выполнения)

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

...