Разница между вызовом виртуальной функции и виртуальные функции?
Это на самом деле вопрос интервью, я не могу понять ответ. Кто-нибудь знает об этом? Вы можете говорить о любой разнице, например, данные, которые толкают в стек.
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 имеет записи дерева
- fun (_ZN7Derived3funEv) переопределить функцию
- fun1 (_ZN4Base4fun1Ev) наследуется от базового класса
- 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++, но если кто-то хотел бы обсудить, то добро пожаловать