2016-04-12 27 views
4

Я самоучка, и поэтому я не знаком с большой терминологией. Кажется, я не могу найти ответ на этот вопрос по поисковому запросу: что такое «виртуальный» и «прямой» вызов виртуальной функции?«Прямой» против «виртуального» вызова виртуальной функции

Это относится к терминологии, а не к технической. Я прошу, когда вызов определяется как «прямо» и «фактически». Это не относится к vtables или чему-либо еще, что связано с реализацией этих понятий.

+0

Не могли бы вы указать, где вы читаете о * direct *. Я впервые услышал об этом. – xvan

+0

https://msdn.microsoft.com/en-us/library/bw1hbe6y.aspx О правилах встраивания компилятора. – antiHUMAN

+1

Возможный дубликат [Что такое различие между вызовом виртуального метода и вызовом Direct Method в контексте VTable?] (Http://stackoverflow.com/questions/12041614/what-is-difference-between-virtual-method-call-and -direct-method-call-in-context) – xvan

ответ

8

Ответ на ваш вопрос отличается на разных концептуальных уровнях.

  • На уровне концептуального языка неформальный термин «виртуальный вызов» обычно относится к вызовам, разрешенных в соответствии с динамического типа объекта, используемого в вызове. Согласно стандарту языка C++ это относится ко всем вызовам виртуальных функций, за исключением вызовов, которые используют квалифицированное имя функции. Когда квалифицированное имя метода используется в вызове, вызов упоминается как «прямой вызов»

    SomeObject obj; 
    SomeObject *pobj = &obj; 
    SomeObject &robj = obj; 
    
    obj.some_virtual_function(); // Virtual call 
    pobj->some_virtual_function(); // Virtual call 
    robj.some_virtual_function(); // Virtual call 
    
    obj.SomeObject::some_virtual_function(); // Direct call 
    pobj->SomeObject::some_virtual_function(); // Direct call 
    robj.SomeObject::some_virtual_function(); // Direct call 
    

    Следует заметить, что часто можно услышать, как люди говорят, что вызовы виртуальных функций, сделанных через непосредственных объектов являются " не виртуальный ". Однако спецификация языка не поддерживает эту точку зрения. Согласно языку, все неквалифицированные вызовы виртуальных функций одинаковы: они разрешаются в соответствии с динамическим типом объекта. В этом [концептуальном] смысле они все virtual.

  • На уровне реализации термин «виртуальный вызов» обычно относится к вызовам, отправленным через определенный механизм реализации, который реализует стандартизованную функциональность виртуальных функций. Обычно он реализуется посредством таблицы виртуальных методов (VMT), связанной с объектом, используемым в вызове. Однако интеллектуальные компиляторы будут использовать VMT только для выполнения вызовов виртуальных функций, когда это действительно необходимо, т. Е. Когда динамический тип объекта неизвестен во время компиляции. Во всех остальных случаях компилятор будет стремиться вызывать метод напрямую, даже если вызов формально «виртуальный» на концептуальном уровне.

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

    SomeObject obj; 
    SomeObject *pobj = &obj; 
    SomeObject &robj = obj; 
    
    obj.some_virtual_function(); // Direct call 
    pobj->some_virtual_function(); // Virtual call in general case 
    robj.some_virtual_function(); // Virtual call in general case 
    
    obj.SomeObject::some_virtual_function(); // Direct call 
    pobj->SomeObject::some_virtual_function(); // Direct call 
    robj.SomeObject::some_virtual_function(); // Direct call 
    

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

+0

Так что вы говорите, что с указателем на объект я могу указать, какой производный (или базовый) класс содержит версию я хочу позвонить? Например, если у меня есть указатель на базовый класс, и я указываю, что хочу вызвать функцию [virtual], которая находится в базовом классе, а не переопределение из какого-либо производного класса, тогда он становится прямым вызовом, потому что его можно решить во время компиляции? – antiHUMAN

+1

@antiHUMAN: Да, если вы хотите подавить виртуальную отправку и вызвать версию базового класса, вы можете сделать это, используя квалифицированное имя. Фактически, это именно тот синтаксис, который вы используете, когда/если вы вызываете версию виртуальной функции базового класса из версии производного класса, например. 'void Derived :: foo() {Base :: foo(); } '. за исключением того, что в этом случае указатель объекта неявный (явная версия будет 'this-> Base :: foo()'). – AnT

+0

Да, я помню, как читал это в «стандартной библии C++» много лет назад, и я понимаю эту концепцию. Ну, это, безусловно, объясняет, почему прямые виртуальные вызовы могут влиять на работу компилятора. Вы дали действительно хороший ответ. – antiHUMAN

5

Предположим, у вас есть этот класс:

class X { 
public: 
    virtual void myfunc(); 
}; 

При вызове виртуальной функции для простого объекта типа X, компилятор будет генерировать прямой вызов, т.е. обращаться непосредственно к X::myfunct():

X a;   // object of known type 
a.myfunc(); // will call X::myfunc() directly  

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

X *pa;  // pointer to a polymorphic object 
...   // initialise the pointer to point to an X or a derived class from X 
pa->myfunc(); // will call the myfunc() that is related to the real type of object pointed to  

Вот вам online simulation кода , Вы увидите, что в первом случае сгенерированная сборка вызывает адрес функции, тогда как во втором случае компилятор загружает что-то в регистр и совершает косвенный вызов с использованием этого регистра (т. Е. Вызываемый адрес не является «сложным» -wired "и будет определяться динамически во время выполнения).

+0

Пожалуйста, включите объяснение, кроме кода, который подробно описывает различия в техническом языке. –

+0

Я не уверен, что это обычно означает «прямой» вызов функции. Вызов 'a.myfunc()' все равно может быть вызовом виртуальной функции, и на самом деле, если класс X изменен так, что он наследует от другого класса, который объявляет myfunc как виртуальный, то именно тот, который вызывается myfunc , как в том, является ли он членом X или его базового класса, определенно зависит от того, переопределен ли он в классе X. –

+0

Поскольку «X» не имеет производных классов, почему он считается полиморфным объектом? Также будет ли «последний» сделать его прямым звонком? – antiHUMAN