Предположим, у нас есть такой код:
struct X{
virtual void foo(){}
};
struct Y: X{
void foo() override{}
}
И мы делаем что-то вроде этого:
void test(Y& ref){
Y local_y;
local_y.foo();
ref.foo();
}
В первом вызове (в каком-то компиляторе) первый вызов вообще не будет проходить через vptr
, а второй будет.
Это имело смысл для меня, потому что, насколько я знаю, только ссылки и указатели полиморфны в С++, а обычные переменные - нет, поэтому нет необходимости просматривать таблицу.
Однако причиной этого было то, что это оптимизация компилятора, потому что local_y
находится в стеке. Таким образом, компилятор знал, что не нужно проходить через виртуальную таблицу.
Хотя причины похожи (а может быть и те же), мне интересно - имеет ли смысл то, что я думал? Кроме того, будет ли у local_y
вообще vptr
? (Я предполагаю, что ответ положительный, потому что мы можем сделать Y& new_y=local_y
, а new_y
должен иметь этот указатель).
Зачем ref.foo()
проходить через vptr
? Из вашего примера не похоже, что Y
является полиморфным классом. Вы имели в виду void test(X& ref) {
?
В обоих случаях должна вызываться правильная функция foo
. Как компилятор в конечном итоге вызывает это, является внутренней деталью реализации. Неважно, пройдет ли он через vptr или «не пройдет», а стандарт языка C++ даже не определяет и не требует «vptr».
@ scohe001 Чем Y
не является полиморфным классом? А что, если Y& ref
относится к Z
?
Ах, похоже, наследование полиморфной функции делает вас полиморфным (en.cppreference.com/w/cpp/language/object#Polymorphic_objects). Я не знал об этом. Хороший звонок @Asteroids.
@AsteroidsWithWings Ну, у меня есть класс, который фокусируется именно на этих вещах, так что это не совсем мой выбор.
@EL_9 Мне жаль это слышать :(
Это имело смысл для меня, потому что, насколько я знаю, только ссылки и указатели полиморфны в С++, а обычные переменные - нет, поэтому нет необходимости просматривать таблицу.
имеет ли смысл то, что я подумал?
Нет. Полиморфизм среды выполнения каким-то образом не «отключен» для «обычных переменных». Виртуальная функция — это виртуальная функция, и при вызове виртуальной функции применяется полиморфизм времени выполнения. Период.
Люди любят говорить, что «полиморфизм времени выполнения работает только со ссылками и указателями», потому что синтаксически трудно найти контрпримеры, не отсекая производные части вашего объекта.
В вашем случае вам даже не нужна виртуальная диспетчеризация (вы буквально просто вызываете функцию, которую хотите вызвать), поэтому весь вопрос спорный.
Однако причиной этого было то, что это оптимизация компилятора, потому что local_y находится в стеке. Таким образом, компилятор знал, что не нужно проходить через виртуальную таблицу.
Это имеет больше смысла. Тривиально доказуемо, что объект с именем local_y
имеет динамический тип Y
, потому что его объявление находится прямо в функции. Так что нет необходимости в каких-либо магических выходках, кроме буквального вызова функции foo()
, которая определена в Y
.
Вы можете назвать это «оптимизацией»; Я называю это "компилятор делает свою работу без лишних шагов".
Будет ли у local_y вообще vptr?
Возможно, но только потому, что компилятор мало что выиграет, выборочно изменяя части определений классов на основе локального использования.
Наличие или отсутствие vptr зависит от настроек оптимизации. При -O2 и выше он даже не генерирует y_local
не говоря уже о vptr. См. godbolt.org/z/7cbe3P
Будет ли объект, который не является указателем или ссылкой, проходить через свою динамическую таблицу, когда для него вызывается функция?
@ EL_9 Нет, и поэтому люди ошибочно думают, что полиморфизм «работает только» с указателями. Правда в том, что это необходимо только с указателями. Если динамический тип известен статически, никакой магии не требуется. Также имейте в виду, что vtables — это деталь реализации.
Я советую вам перестать зацикливаться на деталях реализации. Если компилятору не нужно ничего делать, он ничего не сделает. Ваш код описывает программу. Вы не программируете компьютер; это работа вашего компилятора.