#c #function-pointers #virtual-functions #member-pointers
Вопрос:
Я выполнил следующий код.
#include <iostream>
class Base
{
public:
virtual void func()
{
std::cout<<"Base func called"<<std::endl;
}
};
class Derived: public Base
{
public:
virtual void func() override
{
std::cout<<"Derived func called"<<std::endl;
}
};
int main()
{
void (Base::*func_ptr)()=amp;Base::func; //Yes, the syntax is very beautiful.
Base* bptr=new Derived();
(bptr->*func_ptr)();
}
Мой ожидаемый результат был Base func called
таким. Однако вместо этого результат был
Derived func called
Что меня удивило, потому что я думаю , что func_ptr
это должно позволять видеть только Base
участников(потому что я думал, что func_ptr
это не доступ к функции-члену через _vptr
, но адрес самой функции.
Я хотел бы знать, как в этом случае происходит виртуальная отправка(как происходит доступ к виртуальной таблице) и где это поведение определено в стандарте C (я ничего не смог найти)?
Комментарии:
1. возможно, вы захотите проверить codeproject.com/Articles/7150/…
2. Я считаю, что это реализовано как «смещение», поэтому он будет отключаться от любого типа, на который он указывает, с
this
указателя плюс смещение. Поскольку он указывает на фактический производный объект, это и есть выполняемая функция.3. Если вы подумаете об этом, когда вы пишете
bptr->func
, компиляторfunc
Base::func
тоже решает, только тогда во время выполнения он решается на правильный вызов.4. В стандарте нет упоминания о виртуальных таблицах. Они являются деталями реализации конкретных компиляторов. Виртуальные таблицы — это не единственный способ реализации виртуальных методов, просто самый распространенный способ.
5. Указатель на функцию-член-это не то же самое, что указатель. У него достаточно данных для обработки виртуальной диспетчеризации, когда функция-член, на которую он указывает, является виртуальной функцией.
Ответ №1:
Обратитесь к [expr.вызов], в частности, здесь
[Если выбранная функция виртуальна], вызывается ее окончательное переопределение в динамическом типе выражения объекта; такой вызов называется вызовом виртуальной функции
Независимо от того, вызываете ли вы функцию с помощью указателя или с помощью доступа к члену класса, это одно и то же (ссылка); фактическая функция, вызываемая для виртуальной функции, в конечном счете зависит от фактического типа объекта, для которого она вызывается.
Несколько (ненормативных) примечаний в стандарте в разделе [class.virtual] говорят почти то же самое:
[Примечание 3: Интерпретация вызова виртуальной функции зависит от типа объекта, для которого она вызывается (динамический тип)
[Примечание 4: […] вызов виртуальной функции зависит от определенного объекта для определения, какую функцию вызывать.
Если вы хотите знать, как происходит отправка виртуальной функции, вам нужно будет найти конкретную реализацию, потому что » как » не стандартизировано.
(Мне очень понравилась статья «Базовый взгляд на виртуальную таблицу«, в которой показан один из возможных способов ее реализации с помощью C)
Комментарии:
1. Теперь, после редактирования, для меня это имеет больше смысла, спасибо.
2. Можно ли получить указатель функции, который не вызовет виртуальную отправку?
3. @MarekR возьмите указатель для типа срезанного объекта, если вы собираетесь привести его к базовому объекту, и переинтерпретируйте приведение указателя. Это УБ и на твоей совести.
4. @Swift-FridayPie если это UB, то это не решение. Также, пожалуйста, приведите пример, так как ваше описание для меня слишком расплывчатое.
5. @MarekR: Невозможно получить указатель на функцию- член , но, безусловно, можно получить указатель на функцию , если вы напишете другую функцию для переноса вызова по ссылке, которая явно вызовет функцию-член базового класса. Это может работать даже с не захватывающей лямбдой, например: godbolt.org/z/74WvGP698
Ответ №2:
- Ваше понимание неверно.
- Это деталь реализации. Разные компиляторы могут реализовывать его по-разному. Если вы сомневаетесь, посмотрите на сборку. На самом деле это довольно умно в данном конкретном компиляторе. Если значение, хранящееся в указателе на элемент, четное, это прямой указатель на функцию (все функции расположены по четным адресам). Если он нечетный, это смещение в таблице v плюс один. Все смещения vtable также являются четными, поэтому, чтобы добраться до фактического указателя, он уменьшается на 1.
Комментарии:
1. «Ваше понимание неверно» — Вот почему я здесь.