Имитация динамического связывания (есть и другие применения CRTP) для случая, когда базовый класс считает себя полиморфным, но клиентов фактически заботятся только об одном конкретном производном классе. Так, например, у вас могут быть классы, представляющие интерфейс для некоторой функциональности, специфичной для платформы, и любой конкретной платформе потребуется только одна реализация. Смысл шаблона в том, чтобы шаблонизировать базовый класс, чтобы, несмотря на наличие нескольких производных классов, базовый класс знал во время компиляции, какой из них используется.
Это не поможет вам, когда вам действительно нужен полиморфизм во время выполнения, например, когда у вас есть контейнер AbstractWidget*
, каждый элемент может быть одним из нескольких производных классов, и вам придется их перебирать. В CRTP (или любом коде шаблона) base<derived1>
и base<derived2>
не связаны между собой. Следовательно, таковы derived1
и derived2
. Между ними нет динамического полиморфизма, если только у них нет другого общего базового класса, но тогда вы вернулись к тому, с чего начали виртуальные вызовы.
Вы можете получить некоторое ускорение, заменив свой вектор несколькими векторами: по одному для каждого из производных классов, о которых вы знаете, и один для того, когда вы добавляете новые производные классы позже и не обновляете контейнер. Затем addWidget выполняет некоторую (медленную) проверку typeid
или виртуальный вызов виджета, чтобы добавить виджет в правильный контейнер, и, возможно, имеет некоторые перегрузки, когда вызывающая сторона знает класс времени выполнения. Будьте осторожны, чтобы случайно не добавить подкласс WidgetIKnowAbout
к вектору WidgetIKnowAbout*
. fooAll
и barAll
могут по очереди перебирать каждый контейнер, делая (быстрые) вызовы не виртуальных функций fooImpl
и barImpl
, которые затем будут встроены. Затем они зацикливаются на значительно меньшем векторе AbstractWidget*
, вызывая виртуальные функции foo
или bar
.
Это немного грязно и не чисто OO, но если почти все ваши виджеты принадлежат классам, о которых знает ваш контейнер, то вы можете увидеть увеличение производительности.
Обратите внимание, что если большинство виджетов принадлежат классам, о которых ваш контейнер не может знать (например, потому что они находятся в разных библиотеках), то у вас не может быть встроенного (если ваш динамический компоновщик не может быть встроенным. Мой может т). Вы могли бы отбросить накладные расходы на виртуальный вызов, возившись с указателями на функции-члены, но выигрыш почти наверняка будет незначительным или даже отрицательным. Большая часть накладных расходов на виртуальный вызов связана с самим вызовом, а не с виртуальным поиском, и вызовы через указатели функций не будут встроены.
Посмотрите на это по-другому: если код должен быть встроен, это означает, что фактический машинный код должен отличаться для разных типов. Это означает, что вам нужно либо несколько циклов, либо цикл с переключателем в нем, потому что машинный код явно не может изменяться в ПЗУ при каждом проходе цикла, в соответствии с типом некоторого указателя, извлеченного из коллекции.
Ну, я думаю, что, возможно, объект может содержать некоторый asm-код, который цикл копирует в RAM, отмечает исполняемый файл и переходит в него. Но это не функция-член C ++. И это нельзя сделать переносно. И это, вероятно, даже не будет быстрым, что с копированием и аннулированием icache. Вот почему существуют виртуальные звонки ...