Страницы

Поиск по вопросам

среда, 10 октября 2018 г.

Шаблонная виртуальная функция

Поясните, пожалуйста, почему нельзя создать виртуальную шаблонную функцию?
Нашел следующее объяснение
Member function templates cannot be declared virtual. This constraint is imposed because the usual implementation of the virtual function call mechanism uses a fixed-size table with one entry per virtual function. However, the number of instantiations of a member function template is not fixed until the entire program has been translated. Hence, supporting virtual member function templates would require support for a whole new kind of mechanism in C++ compilers and linkers. In contrast, the ordinary members of class templates can be virtual because their number is fixed when a class is instantiated
То есть теоретически такой функционал реализовать можно, но это потребует серьезного изменения принципов работы существующих компилятор и линковщиков. Или есть и другие причины?


Ответ

Простой ответ:
В С++ шаблон функции не является функцией, поэтому шаблон не может быть виртуальным.
В C#/Java/etc используются не шаблоны, а generics. Generic-функция это (одна) функция, поэтому там такой проблемы нет.
Сложный ответ:
В С++ виртуальные функции сделаны так, что их количество прописано в определении базового класса. Это позволяет присвоить функции некоторый индекс в базовом классе и быстро находить ее по этому индексу.
struct Base { func_t* vft; // скрытый член класса - массив виртуальных функций virtual void f(); };
Base* x = new Derived; x->f(); // компилируется в x->vft[0]();
Если шаблоны будут виртуальными, то вместо перечисления функций в базовом классе надо искать все подстановки шаблона при вызовах функции. Для этого вместо индексов надо использовать имена, и искать эти имена в хеш-таблице.
struct Base { hash_map vft; // скрытый член класса - хеш-таблица виртуальных функций template virtual void f(); };
Base* x = new Derived; x->f(); // компилируется в x->vft["f"]();
Скорость вызова значительно упадет, т.к. надо будет разрешать коллизии.
Можно использовать идеальную хеш-функцию (без коллизий).
struct Base { func_t* vft; // скрытый член класса - массив (sic!) виртуальных функций virtual void f(); };
Base* x = new Derived; x->f(); // компилируется в x->vft[ideal_hash("f")](); // ideal_hash(name) выдает индекс массива, без коллизий
Но из-за динамической линковки (.so/.dll) весь исходный код программы недоступен, и при каждой загрузке SO/DLL надо останавливать всю программу, менять хеш-функцию и перестраивать все таблицы, чтобы учитывались типы, которые добавились в этой SO/DLL.

Использование JIT-компилятора может заменять виртуальные вызовы на обычные, и тогда никаких проблем с производительностью вызова не будет.
// вместо x->f(); генерируется x->Derived::f(); // если доказано что тут может быть только Derived
Но девиртуализация работает только если количество классов мало, и на данный момент эффективных JIT-компиляторов нет. (Те что есть, например в LLVM, не показывают хороших результатов.)

Комментариев нет:

Отправить комментарий