#c #templates #virtual-functions
#c #шаблоны #виртуальные функции
Вопрос:
От Эддисона Уэсли: Шаблоны C
Шаблоны функций-членов не могут быть объявлены виртуальными. Это ограничение вводится потому, что обычная реализация механизма вызова виртуальной функции использует таблицу фиксированного размера с одной записью на виртуальную функцию. Однако количество экземпляров шаблона функции-члена не является фиксированным до тех пор, пока не будет переведена вся программа.
Означает ли приведенная выше цитата, что шаблоны имеют статическую привязку, а виртуальные функции имеют динамическую привязку, по этой причине не может быть шаблонов виртуальных функций? Пожалуйста, посмотрите, возможно ли объяснение на языке непрофессионала.
Комментарии:
1. Зависит от того, что вы подразумеваете под привязкой. Вы можете реализовать виртуальный метод, вызвав шаблон-член. Пока вы его встроите, любой компилятор с оптимизацией конечных вызовов устранит накладные расходы
2. Вопреки условиям SO, просто хотел сказать, что это чертовски хороший вопрос .
Ответ №1:
И да, и нет.
Наиболее популярным методом разрешения вызовов виртуальных функций является использование таблицы («vtable»), где каждая виртуальная функция сопоставляется индексу в таблице. Для этого более или менее требуется, чтобы вы знали размер таблицы.
С помощью шаблонов новые функции будут создаваться по мере необходимости в разных модулях. Тогда вам пришлось бы либо убедить компоновщик создать таблицу после определения конечного числа функций, либо использовать какую-либо структуру среды выполнения для поиска доступных функций во время выполнения.
Во многих системах компоновщик является частью ОС и ничего не знает о C , поэтому этот параметр ограничен. Поиск во время выполнения, конечно, негативно повлияет на производительность, возможно, для всех виртуальных функций.
Итак, в конце концов, было решено, что просто не стоило труда вводить виртуальные шаблоны в язык.
Комментарии:
1. Спасибо за беспокойство, вы сказали «С помощью шаблонов новые функции будут создаваться по мере необходимости в разных модулях». Я знаю, что я упускаю какой-то момент, разве мы не используем шаблоны, потому что мы не хотим создавать разные функции, мы хотим использовать одну и ту же функцию со случайными параметрами? Я знаю, вы имели в виду что-то другое, пожалуйста, уточните.
2. doh: теперь я прочитал сообщение @ Tony ниже, и он, кажется, проясняет мою точку зрения выше 🙂
Ответ №2:
Рассмотрим:
struct X
{
template <typename T>
T incr(const Tamp; t)
{
return t 1;
}
};
Поскольку incr()
применяется к различным типам T, генерируются новые функции. Скажем, внутри app.c
у вас есть:
X x;
x.incr(7); // incr<int>()
x.incr(7.0); // incr<double>()
x.incr("hello"); // incr<const char*>()
Затем, во время компиляции app.c
, он видит 3 функции, которые — если бы incr
им было разрешено быть virtual
— могли бы освободить место для трех приведенных выше экземпляров в таблице виртуальной отправки для X. Затем допустим, что он загружает разделяемую библиотеку во время выполнения, и код для этой библиотеки имел 2 экземпляра X::incr
for uint32_t
и std::string::const_iterator
. dlopen()
потребуется расширить существующую виртуальную таблицу отправки для уже созданных объектов, чтобы освободить место для двух новых функций. Звучит не слишком ужасно, но учтите:
-
каждый бит кода, вызывающий виртуальные функции, должен знать, был ли адрес этих функций изменен на некоторое смещение во время выполнения (из-за динамической загрузки дополнительных экземпляров), поэтому при каждой виртуальной отправке возникает дополнительная память и затраты на производительность
-
при наличии множественного наследования или производного класса, из которого сам является производным, компилятор может захотеть создать единую таблицу виртуальной диспетчеризации для всего набора виртуальных функций (один из вариантов, для реализации виртуальной диспетчеризации существует множество): в этом случае новые виртуальные функции либо вытеснят виртуальные функции других классов, либо должны быть отделены от существующих. Опять же, для управления этим требуется больше времени выполнения в любой схеме.
Таким образом, очень редкие случаи, когда это может быть полезно, не стоят того, чтобы идти на компромисс и усложнять более распространенный случай без шаблонов виртуальных.
Ответ №3:
Означает ли приведенная выше цитата, что шаблоны имеют статическую привязку, а виртуальные функции имеют динамическую привязку, по этой причине не может быть шаблонов виртуальных функций?
В принципе, да. Более конкретно, статическая привязка вызывает проблему, когда генерируется код для поддержки динамической привязки.
Когда компилятор компилирует базовый класс, он находит виртуальную функцию и решает создать таблицу виртуальных функций — это будет использоваться для реализации динамического связывания: когда виртуальная функция вызывается в производном экземпляре, скомпилированный код следует за указателем в экземпляре на таблицу виртуальных функций для производного класса, затем за указателем в этой таблице на реализацию виртуальной функции. Эта таблица должна включать все возможные виртуальные функции, которые могут быть вызваны. Теперь предположим, что мы создали шаблонную виртуальную функцию. В таблице функций потребуется запись для каждого экземпляра шаблона, потому что любая из этих функций может быть вызвана во время выполнения. Но информация о том, с какими типами создается экземпляр шаблона, не может (в общем случае) быть собрана во время создания таблицы виртуальных функций. (По крайней мере, не без использования модели компиляции C .)
Комментарии:
1. спасибо, ваш ответ был проще для понимания, Вопрос: vtable создается компилятором, поэтому, когда вы говорите, что привязка выполняется во время выполнения, это означает, что во время выполнения компоновщик ссылается на таблицу, чтобы увидеть, какая функция где находится, это правильно?
2. и в случае шаблонов мы не можем знать во время компиляции, какой экземпляр шаблона будет вызван, поэтому мы не можем поместить их в виртуальную таблицу, вы это имеете в виду?
3. Компоновщик ничего не делает во время выполнения. Это часть процесса компиляции. Во время компиляции компилятор генерирует некоторый код. Во время выполнения этот код ссылается на таблицу.
Ответ №4:
виртуальные функции и шаблоны по-прежнему прекрасно работают вместе, есть только небольшой частный случай, который не реализован.
template<class T>
class A { virtual void f()=0; }; // works fine
class A { template<class T> virtual void f(T t)=0; }; // does not work
Ответ №5:
Зависит от того, что вы подразумеваете под привязкой.
Вы можете реализовать виртуальный метод, вызвав шаблон-член. Пока вы их встроите, любой компилятор с оптимизацией конечных вызовов устранит накладные расходы
Ответ №6:
Вроде того.
На самом деле вы не можете «переопределить» деинсталлированный template
, потому что он даже не существует в скомпилированном приложении. Если вы создаете его экземпляр, то вы переопределяете не шаблон, а просто другую обычную функцию. 🙂