Виртуальная функция Templatized
Мы знаем, что C++ не допускает шаблонную виртуальную функцию в классе. Кто-нибудь понимает, почему такое ограничение?
4 ответа
Краткий ответ: Виртуальные функции - это не знание, кто кого вызывал, до тех пор, пока во время выполнения не будет выбрана функция из уже скомпилированного набора функций-кандидатов. Шаблоны функций, OTOH, предназначены для создания произвольного числа различных функций (с использованием типов, которые могут даже не быть известны при написании вызываемого) во время компиляции со стороны вызывающих. Это просто не соответствует.
Несколько более длинный ответ: Виртуальные функции реализованы с использованием дополнительной косвенной ссылки (Общее универсальное лечение программиста), обычно реализуемой в виде таблицы указателей на функции (так называемая таблица виртуальных функций, часто сокращаемая как "vtable"). Если вы вызываете виртуальную функцию, система во время выполнения выберет нужную функцию из таблицы. Если бы существовали шаблоны виртуальных функций, система времени выполнения должна была бы найти адрес уже скомпилированного экземпляра шаблона с точными параметрами шаблона. Поскольку конструктор класса не может предоставить произвольное количество экземпляров шаблона функции, созданных из неограниченного набора возможных аргументов, это не может работать.
Как бы вы построили vtable? Теоретически у вас может быть бесконечное количество версий вашего шаблонного члена, и компилятор не будет знать, какими они могут быть при создании vtable.
В других ответах уже упоминалось, что виртуальные функции обычно обрабатываются в C++, имея в объекте указатель (vptr) на таблицу. Эта таблица (vtable) содержит указатель на функции, которые будут использоваться для виртуальных членов, а также некоторые другие вещи.
Другая часть объяснения состоит в том, что шаблоны обрабатываются в C++ путем расширения кода. Это позволяет явную специализацию.
Теперь некоторые языки требуют (Eiffel - я думаю, что это также относится к Java и C#, но мои знания о них недостаточно хороши, чтобы быть авторитетными) или разрешают (Ada) совместную обработку универсальности, не имеют явного специализация, но позволила бы функцию виртуального шаблона, помещая шаблон в библиотеки и могла бы уменьшить размер кода.
Вы можете получить эффект общей универсальности, используя технику, которая называется стирание типов. Это делает вручную то, что делают компиляторы для языка общего генерирования (ну, по крайней мере, некоторые из них, в зависимости от языка, могут быть возможны другие методы реализации). Вот (глупый) пример:
#include <string.h>
#include <iostream>
#ifdef NOT_CPP
class C
{
public:
virtual template<typename T> int getAnInt(T const& v) {
return getint(v);
}
};
#else
class IntGetterBase
{
public:
virtual int getTheInt() const = 0;
};
template<typename T>
class IntGetter: public IntGetterBase
{
public:
IntGetter(T const& value) : myValue(value) {}
virtual int getTheInt() const
{
return getint(myValue);
}
private:
T const& myValue;
};
template<typename T>
IntGetter<T> makeIntGetter(T const& value)
{
return IntGetter<T>(value);
}
class C
{
public:
virtual int getAnInt(IntGetterBase const& v)
{
return v.getTheInt();
}
};
#endif
int getint(double d)
{
return static_cast<int>(d);
}
int getint(char const* s)
{
return strlen(s);
}
int main()
{
C c;
std::cout << c.getAnInt(makeIntGetter(3.141)) + c.getAnInt(makeIntGetter("foo")) << '\n';
return 0;
}
Я думаю, это так, что компиляторы могут генерировать смещения vtable в качестве констант (тогда как ссылки на не виртуальные функции являются исправлениями).
Когда вы компилируете вызов функции шаблона, компилятор обычно просто помещает заметку в двоичный файл, фактически говоря компоновщику: "Пожалуйста, замените эту заметку указателем на правильную функцию". Статический компоновщик делает нечто подобное, и в конечном итоге загрузчик заполняет значение, как только код загружен в память и его адрес известен. Это называется исправлением, поскольку загрузчик "исправляет" код, заполняя необходимые ему числа. Обратите внимание, что для генерации исправления компилятору не нужно знать, какие другие функции существуют в классе, ему просто нужно знать произвольное имя нужной ему функции.
Однако с виртуальными функциями компилятор обычно генерирует код, говорящий: "получить указатель vtable из объекта, добавить 24 к нему, загрузить адрес функции и вызвать его". Чтобы знать, что конкретная виртуальная функция, которую вы хотите, имеет смещение 24, компилятор должен знать обо всех виртуальных функциях в классе и в каком порядке они будут отображаться в виртуальной таблице. В настоящее время компилятор знает это, потому что все виртуальные функции перечислены прямо в определении класса. Но для того, чтобы сгенерировать виртуальный вызов, в котором есть шаблонные виртуальные функции, компилятору необходимо знать в момент вызова, какие существуют экземпляры шаблона функции. Он не может этого знать, потому что разные модули компиляции могут создавать разные версии шаблона функции. Так что не удалось определить, какое смещение использовать в vtable.
Теперь я подозреваю, что компилятор может поддерживать шаблоны виртуальных функций, испуская вместо постоянного смещения vtable целочисленное исправление. То есть записка, в которой говорится: "Пожалуйста, заполните смещение виртуальной функции виртуальной функцией этим именем". Затем статический компоновщик может заполнить фактическое значение, как только он узнает, какие экземпляры доступны (в тот момент, когда он удаляет дублирующиеся экземпляры шаблона в разных единицах компиляции). Но это налагает серьезную нагрузку на компоновщик, чтобы выяснить компоновку vtable, что в настоящее время компилятор делает сам. Шаблоны были специально определены, чтобы упростить процесс реализации, в надежде, что они действительно появятся в дикой природе за некоторое время до C++0x...
Итак, я полагаю, что некоторые рассуждения в этом направлении заставили комитет по стандартам сделать вывод, что шаблоны виртуальных функций, даже если они вообще могут быть реализованы, слишком сложны для реализации и поэтому не могут быть включены в стандарт.
Обратите внимание, что в приведенном выше тексте есть немало предположений еще до того, как я попытаюсь прочесть мысли комитета: я не пишу реализацию C++ и не играю ее на телевидении.