Я получил этот вопрос, когда получил комментарий к коду, в котором говорилось, что виртуальные функции не обязательно должны быть встроенными.
Я думал, что встроенные виртуальные функции могут пригодиться в тех случаях, когда функции вызываются непосредственно для объектов. Но мне пришёл в голову контраргумент: зачем нужно определять виртуальные, а затем использовать объекты для вызова методов?
Не лучше ли использовать встроенные виртуальные функции, так как они почти никогда не расширяются?
Фрагмент кода, который я использовал для анализа:
class Temp
{
public:
virtual ~Temp()
{
}
virtual void myVirtualFunction() const
{
cout<<"Temp::myVirtualFunction"<<endl;
}
};
class TempDerived : public Temp
{
public:
void myVirtualFunction() const
{
cout<<"TempDerived::myVirtualFunction"<<endl;
}
};
int main(void)
{
TempDerived aDerivedObj;
//Compiler thinks it's safe to expand the virtual functions
aDerivedObj.myVirtualFunction();
//type of object Temp points to is always known;
//does compiler still expand virtual functions?
//I doubt compiler would be this much intelligent!
Temp* pTemp = &aDerivedObj;
pTemp->myVirtualFunction();
return 0;
}
pTemp->myVirtualFunction()
может быть разрешен как не виртуальный вызов, он может иметь встроенный вызов. Этот ссылочный вызов встроен в g ++ 3.4.2:TempDerived & pTemp = aDerivedObj; pTemp.myVirtualFunction();
ваш код - нет.Ответы:
Виртуальные функции могут быть встроены иногда. Отрывок из отличного FAQ по C ++ :
источник
C ++ 11 добавил
final
. Это меняет принятый ответ: больше не нужно знать точный класс объекта, достаточно знать, что у объекта есть хотя бы тот тип класса, в котором функция была объявлена как финальная:источник
icc
кажется, сделать это, по этой ссылке.Существует одна категория виртуальных функций, в которой все еще имеет смысл иметь встроенные функции. Рассмотрим следующий случай:
Вызов для удаления «base», будет выполнять виртуальный вызов для вызова правильного деструктора производного класса, этот вызов не является встроенным. Однако, поскольку каждый деструктор вызывает свой родительский деструктор (который в этих случаях пуст), компилятор может встроить эти вызовы, так как они не вызывают функции базового класса виртуально.
Тот же принцип существует для конструкторов базовых классов или для любого набора функций, где производная реализация также вызывает реализацию базовых классов.
источник
Я видел компиляторы, которые не генерируют v-таблицу, если вообще не существует не встроенной функции (и определено в одном файле реализации вместо заголовка). Они будут выдавать ошибки вроде
missing vtable-for-class-A
или что-то подобное, и вы будете в замешательстве, как я.Действительно, это не соответствует Стандарту, но это происходит, поэтому подумайте над тем, чтобы поместить хотя бы одну виртуальную функцию не в заголовок (если только виртуальный деструктор), чтобы компилятор мог создать виртуальную таблицу для этого класса. Я знаю, что это происходит с некоторыми версиями
gcc
.Как кто-то упоминал, встроенные виртуальные функции иногда могут быть полезны , но, конечно, чаще всего вы будете использовать их, когда не знаете динамический тип объекта, потому что это было главной причиной
virtual
в первую очередь.Однако компилятор не может полностью игнорировать
inline
. У него есть другая семантика, кроме ускорения вызова функции. Неявные рядный для определения в классе является механизмом , который позволяет поместить определение в заголовок: толькоinline
функции могут быть определены несколько раз на протяжении всей программы без нарушения каких - либо правил. В конце концов, он ведет себя так, как вы бы определили его только один раз во всей программе, даже если вы включили заголовок несколько раз в разные файлы, связанные вместе.источник
Ну, на самом деле виртуальные функции всегда могут быть встроенными , если они статически связаны друг с другом: предположим, у нас есть абстрактный класс
Base
с виртуальной функциейF
и производными классамиDerived1
иDerived2
:Гипотетический вызов
b->F();
(сb
типомBase*
), очевидно, виртуальный. Но вы (или компилятор ...) могли бы переписать его примерно так (предположим, чтоtypeof
этоtypeid
-подобная функция, которая возвращает значение, которое можно использовать вswitch
)в то время как нам все еще нужен RTTI для
typeof
вызова, он может быть эффективно встроен путем встраивания виртуальной таблицы в поток команд и специализации вызова для всех участвующих классов. Это также можно обобщить, специализируя лишь несколько классов (скажем, простоDerived1
):источник
Маркировка виртуального метода как встроенного помогает в дальнейшей оптимизации виртуальных функций в следующих двух случаях:
Любопытно повторяющийся шаблон ( http://www.codeproject.com/Tips/537606/Cplusplus-Prefer-Curiously-Recurring-Template-Patt )
Замена виртуальных методов шаблонами ( http://www.di.unipi.it/~nids/docs/templates_vs_inheritance.html )
источник
inline действительно ничего не делает - это подсказка. Компилятор может игнорировать это или встроить событие вызова без встроенного, если он видит реализацию и ему нравится эта идея. Если на карту поставлена ясность кода, встроенный код следует удалить.
источник
Объявленные объявленные виртуальные функции встроены при вызове через объекты и игнорируются при вызове через указатель или ссылки.
источник
С современными компиляторами, это не принесет никакого вреда, чтобы не включить их. Некоторые древние комбинации компилятора / компоновщика могли создать несколько vtables, но я не думаю, что это проблема больше.
источник
Компилятор может встроить функцию только тогда, когда вызов может быть однозначно разрешен во время компиляции.
Виртуальные функции, однако, разрешаются во время выполнения, и поэтому компилятор не может встроить вызов, так как при типе компиляции динамический тип (и, следовательно, вызываемая реализация функции) не может быть определен.
источник
В случаях, когда вызов функции однозначен и функция является подходящим кандидатом для встраивания, компилятор достаточно умен, чтобы встроить код в любом случае.
В остальное время «встроенный виртуальный» - это нонсенс, и действительно, некоторые компиляторы не будут компилировать этот код.
источник
Имеет смысл создавать виртуальные функции и затем вызывать их для объектов, а не для ссылок или указателей. Скотт Мейер рекомендует в своей книге «эффективный с ++» никогда не переопределять унаследованную не виртуальную функцию. Это имеет смысл, потому что когда вы создаете класс с не-виртуальной функцией и переопределяете функцию в производном классе, вы можете быть уверены, что используете его правильно самостоятельно, но вы не можете быть уверены, что другие будут использовать его правильно. Кроме того, вы можете позже использовать его неправильно. Итак, если вы делаете функцию в базовом классе и хотите, чтобы она была перенаправляемой, вы должны сделать ее виртуальной. Если имеет смысл создавать виртуальные функции и вызывать их для объектов, также имеет смысл встроить их.
источник
На самом деле в некоторых случаях добавление «inline» к виртуальному окончательному переопределению может сделать ваш код некомпилируемым, поэтому иногда есть разница (по крайней мере, при использовании компилятора VS2017)!
На самом деле я делал виртуальную встроенную функцию окончательного переопределения в VS2017, добавляя стандарт c ++ 17 для компиляции и компоновки, и по какой-то причине это не удалось, когда я использую два проекта.
У меня был тестовый проект и библиотека реализации, которую я тестировал. В тестовом проекте у меня есть файл "linker_include.cpp", который #include * .cpp файлы из другого проекта, которые необходимы. Я знаю ... Я знаю, что могу настроить msbuild для использования объектных файлов из DLL, но имейте в виду, что это решение для Microsoft, в то время как включение файлов cpp не связано с системой сборки и намного проще для версии файл cpp, чем XML-файлы и настройки проекта и тому подобное ...
Интересно то, что я постоянно получал ошибку компоновщика из тестового проекта. Даже если я добавлю определение отсутствующих функций копией-вставкой, а не через include! Так странно. Другой проект создан, и между ними нет никакой связи, кроме маркировки ссылки на проект, поэтому существует порядок сборки, гарантирующий, что оба будут построены ...
Я думаю, что это какая-то ошибка в компиляторе. Я понятия не имею, существует ли он в компиляторе, поставляемом с VS2020, потому что я использую более старую версию, потому что некоторые SDK работают только с этим должным образом :-(
Я просто хотел добавить, что не только маркировка их как встроенных может что-то значить, но может даже заставить ваш код не собираться в некоторых редких случаях! Это странно, но приятно знать.
PS: код, над которым я работаю, связан с компьютерной графикой, поэтому я предпочитаю встраивание, поэтому я использовал как final, так и inline. Я сохранил окончательный спецификатор, чтобы надеяться, что сборка релиза будет достаточно умной, чтобы построить DLL, даже если я прямо не намекаю, так что ...
PS (Linux) .: Я ожидаю, что в gcc или clang не происходит то же самое, что я обычно делал для подобных вещей. Я не уверен, откуда возникла эта проблема ... Я предпочитаю делать c ++ на Linux или, по крайней мере, с некоторым gcc, но иногда проект отличается по потребностям.
источник