встроенная виртуальная функция

Насколько я понимаю, в C ++ виртуальная функция может быть встроена, но, как правило, указание на встроенную функцию игнорируется. Кажется, что встроенные виртуальные функции не имеют особого смысла.

Это правильно?

Может ли кто-нибудь привести случай, когда встроенная виртуальная функция хороша?


person skydoor    schedule 25.01.2010    source источник
comment
Я не вижу здесь настоящего вопроса ...   -  person Mitch Wheat    schedule 25.01.2010
comment
привет, что ты имеешь в виду? Я не обратил внимания на проблему ясно? Мне жаль насчет того.   -  person skydoor    schedule 25.01.2010
comment
Встроенные функции никогда не нужны, поэтому будет сложно показать вам необходимую встроенную виртуальную.   -  person zneak    schedule 25.01.2010
comment
to zneak: вау ... это правда. Большое спасибо.   -  person skydoor    schedule 25.01.2010
comment
возможный дубликат Действительно ли встроенные виртуальные функции бессмысленны?   -  person Theodore Murdock    schedule 10.04.2014


Ответы (3)


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

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

person Jerry Coffin    schedule 25.01.2010
comment
Я также считаю, что очень короткие функции более понятны, когда они встроены в тело класса. - person Omnifarious; 25.01.2010
comment
Вопрос касается ключевого слова inline. - person Georg Fritzsche; 25.01.2010
comment
Кроме того, я думаю, что если вся ваша виртуальная функция вызывает какую-то другую функцию (что может произойти с множественным наследованием или по другим причинам), компилятор может просто поместить thunking для этого прямо в vtable. - person Omnifarious; 25.01.2010
comment
@gf, нет, это касается концепции встраивания виртуальной функции. Функции, объявленные в теле класса, неявно объявляются встроенными, даже без ключевого слова inline. - person Omnifarious; 25.01.2010
comment
Достаточно честно, но это не обязательно означает встроенный в тело класса. - person Georg Fritzsche; 25.01.2010

Чтобы полностью ответить на этот вопрос, нужно понимать, что свойство быть virtual независимо от самой функции и вызовов, сделанных к этой функции. Есть виртуальные и не виртуальные функции. Существуют виртуальные и невиртуальные вызовы этих функций.

То же самое и со свойством быть inline. Есть iniline и non-inline функции. И есть встроенные и невстроенные вызовы этих функций.

Эти свойства - virtual и inline - при применении к самой функции не конфликтуют. У них просто нет причин и шансов на конфликт. Единственное, что спецификатор inline изменяет для самой функции, так это то, что он изменяет правило единого определения для этой функции: функция может быть определена в нескольких единицах перевода (и она должна быть определена в каждой единице перевода, в которой она используется). Единственное, что изменяется в спецификаторе virtual, - это то, что класс, содержащий эту функцию, становится полиморфным. Это не оказывает реального влияния на саму функцию.

Таким образом, нет абсолютно никаких проблем с одновременным объявлением функций virtual и inline. Нет никаких оснований для конфликта. Это совершенно законно на языке C ++.

struct S {
  virtual void foo(); 
};

inline void S::foo() // virtual inline function - OK, whatever
{
}

Однако, когда люди задают этот вопрос, их обычно интересуют не свойства самой функции, а характеристики вызовов функции.

Определяющей особенностью виртуального вызова является то, что он разрешается во время выполнения, что означает, что обычно невозможно встроить истинные виртуальные вызовы:

S *s = new SomeType;
s->foo(); // virtual call, in general case cannot be inlined

Однако, если вызов сам по себе не является виртуальным (даже если он переходит к виртуальной функции), встраивание вообще не проблема:

S *s = new SomeType;
s->S::foo(); // non-virtual call to a virtual function, can easily be inlined

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

S ss;
ss.foo(); // formally a virtual call, but in practice it can easily be inlined

В некоторых случаях это сложнее, но все же выполнимо:

S *s = new S;
s->foo(); // virtual call, but a clever compiler might be able
          // to figure out that it can be inlined
person AnT    schedule 25.01.2010
comment
Как можно сказать, что ss.foo(); формально является виртуальным звонком? - person Belloc; 06.03.2017
comment
@Belloc: Потому что в C ++ все вызовы виртуальных функций являются виртуальными вызовами, т.е. разрешаются в соответствии с динамическим типом объекта. Ничто в спецификации языка не говорит о том, что ss.foo() является чем-то особенным или отличным. Единственный вызов, в котором динамический тип игнорируется, - это вызов с квалифицированным именем. - person AnT; 06.03.2017
comment
Это не то, что я могу увидеть здесь. Дизассемблирование кода показывает, что f->bar() - это виртуальный вызов, как и ожидалось, а d.bar() - не виртуальный вызов. - person Belloc; 07.03.2017
comment
@Belloc: То, что вы видите в дизассемблировании, не имеет никакого отношения. Это просто означает, что ваш компилятор оптимизировал косвенный вызов в прямой вызов, ни больше ни меньше. Между тем я говорю об этом звонке на языковом уровне. А на уровне языка это виртуальный звонок. - person AnT; 07.03.2017
comment
Я не эксперт в gcc и clang, но, насколько я понимаю, -O0 - это самый низкий уровень оптимизации для обоих компиляторов. Кстати, такой же результат вы получите для clang с переключателем -O0. В любом случае, не могли бы вы привести цитату из Стандарта, подтверждающую то, что вы говорите? - person Belloc; 07.03.2017
comment
@Belloc: нет настройки компилятора, которая требовала бы отключить все оптимизации. Даже в режиме -O0 компилятор по-прежнему будет использовать множество оптимизирующих приемов. Что касается стандартной цитаты, она прямо здесь в 5.2.2 Вызов функции: 1 [...] Если выбранная функция не виртуальная, или если идентификатор -выражение в выражении доступа к члену класса является квалифицированным-идентификатором, вызывается эта функция. В противном случае вызывается его последний переопределитель (10.3) в динамическом типе объектного выражения; такой вызов называется вызовом виртуальной функции. - person AnT; 07.03.2017
comment
@Belloc: Более того, по второму разу я бы даже не называл это оптимизацией. Настоящая оптимизация компилятора происходит, когда компилятор генерирует код, который отклоняется от поведения абстрактной машины C ++ (конечно, все еще сохраняя наблюдаемое поведение). В этом случае отклонений нет. Вызов разрешается в соответствии с динамическим типом объекта, точно так же, как это сделала бы абстрактная машина C ++. Совершенно неважно, отправляется ли вызов прямо или косвенно (то есть то, что вы видите при разборке). - person AnT; 07.03.2017
comment
Мне нужно уйти сейчас. Я вернусь завтра. Большое спасибо за внимание, отвечая на мои комментарии. - person Belloc; 07.03.2017
comment
Вы убедили меня с помощью [expr.call] / 1. Еще раз большое спасибо (+1). - person Belloc; 07.03.2017

Вы можете иметь виртуальные функции как встроенные. Решение сделать вызов функции встроенным принимается не только во время компиляции. Это может быть в любое время между компиляцией и рутаймом. Вы можете обратиться к этой статье в Herb Sutter. Встроенный Redux

person Jagannath    schedule 25.01.2010