"Прямой" vs "виртуальный" вызов виртуальной функции

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

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

2 ответов


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

  • на уровне концептуального языка неофициальный термин "виртуальный вызов" обычно относится к вызовам, разрешенным в соответствии с динамического типа объекта, используемого в вызове. Согласно стандарту языка 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
    

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

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


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

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    

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