Warning: include(/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/top-cache.php): failed to open stream: No such file or directory in /volume1/web/cyberhost.biz/index.php on line 9 Call Stack: 0.0001 356256 1. {main}() /volume1/web/cyberhost.biz/index.php:0 Warning: include(): Failed opening '/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/top-cache.php' for inclusion (include_path='.:/usr/share/pear') in /volume1/web/cyberhost.biz/index.php on line 9 Call Stack: 0.0001 356256 1. {main}() /volume1/web/cyberhost.biz/index.php:0 [Из песочницы] Преобразование обычного класса в странно повторяющийся шаблон | Хостинг за 90 р. от cyberhost.biz — платный хостинг
+7 993 930-19-90 suport@cyberhost.biz

Некоторое время назад ко мне подошли программисты, недавно начавшие изучать с++ и привыкшие к процедурному программированию, с жалобой на то, что механизм вызова виртуальных методов «тормозит». Я был очень удивлён.

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

Итого имеем следующие дополнительные затраты:

1) Дополнительный указатель в классе (указатель на таблицу виртуальных методов);
2) Дополнительный код в конструкторе класса (для инициализации виртуального табличного указателя);
3) Дополнительный код при каждом вызове виртуального метода (разыменование указателя на таблицу виртуальных методов и поиск по таблице нужного адреса виртуального метода).

К счастью, компиляторы сейчас поддерживают такую оптимизацию как девиртуализация (devirtualization). Суть её заключается в том, что виртуальный метод вызывается напрямую, если компилятор точно знает тип вызываемого объекта и таблица виртуальных методов при этом не используется. Такая оптимизация появилась довольно давно. Например, для gcc — начиная с версии 4.7, для clang’a начиная с версии 3.8 (появился флаг -fstrict-vtable-pointers).

Но всё же, можно ли пользоваться полиморфизмом без виртуальных функций вообще? Ответ: да, можно. На помощь приходит так называемый «странно повторяющийся шаблон» (Curiously Recurring Template Pattern или CRTP).

Давайте рассмотрим пример преобразования класса с виртуальными методами в класс с шаблоном:

class IA {
public:
virtual void helloFunction() = 0;
};
class B : public IA {
public:
void helloFunction(){
std::cout<< "Hello from B";
}
};
Превращается в:

template <typename T>
class IA {
public:
void helloFunction(){
static_cast<T*>(this)->helloFunction();
}
};
class B : public IA<B> {
public:
void helloFunction(){
std::cout<< "Hello from B";
}
};
Обращение:

template <typename T>
void sayHello(IA<T>* object) {
object->helloFunction();
}
Класс IA принимает шаблоном порождённый класс и кастует указатель на this к порождённому классу. static_cast производит проверку приведения на уровне компиляции, следовательно, не влияет на производительность. Класс B порождён от класса IA, который в свою очередь шаблонизирован классом B.

Дополнительные затраты — дополнительный указатель в классе, дополнительный код в конструкторе класса, дополнительный код при каждом вызове виртуального метода, как в первом случае отсутствуют. Если ваш компилятор не поддерживает оптимизацию девиртуализации, то такой код будет работать быстрее и занимать меньше памяти.

Спасибо за внимание.

Надеюсь, кому-нибудь заметка будет полезна.

Warning: include(/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/bottom-cache.php): failed to open stream: No such file or directory in /volume1/web/cyberhost.biz/index.php on line 13 Call Stack: 0.0001 356256 1. {main}() /volume1/web/cyberhost.biz/index.php:0 Warning: include(): Failed opening '/volume1/web/cyberhost.biz/wp-content/plugins/jaster_cahce/cache/bottom-cache.php' for inclusion (include_path='.:/usr/share/pear') in /volume1/web/cyberhost.biz/index.php on line 13 Call Stack: 0.0001 356256 1. {main}() /volume1/web/cyberhost.biz/index.php:0