В моей работе много циклов с множеством внутренних вызовов функций; производительность здесь критична, а накладные расходы на вызовы виртуальных функций недопустимы, поэтому я стараюсь избегать динамического полиморфизма с помощью CRTP, вот так:
template<class DType>
struct BType {
DType& impl(){ return *static_cast<DType*>(this); }
void Func(){ impl().Func(); }
};
struct MyType : public BType<MyType> {
void Func(){ /* do work */ }
};
template<class DType>
void WorkLoop(BType<DType>* func){
for (int i=0;i<ni;++i){ func->func(); }
}
struct Worker {
void DoWork(){ WorkLoop(&thing) };
private:
MyType thing;
};
Worker worker;
worker.DoWork();
Кроме того: правильный ли способ на самом деле использовать класс CRTP? Теперь мне нужно, чтобы фактический тип зависел от пользовательской опции во время выполнения, и обычно динамический полиморфизм с абстрактным базовым классом / шаблоном стратегии был бы правильным дизайном, но я не могу позволить себе вызовы виртуальных функций. Один из способов сделать это, кажется, с помощью некоторого ветвления:
struct Worker {
void DoWork(){
if (option=="optionA"){
TypeA thing;
WorkLoop(thing); }
else if (option=="optionB"){
TypeB thing;
WorkLoop(thing); }
...
Но это похоже на паршивый дизайн. Передача его в качестве параметра шаблона здесь (или использование дизайна на основе политики) выглядит как вариант:
template<class T>
struct Worker {
void DoWork(){ WorkLoop(&thing) };
T thing;
};
if (option=="optionA"){
Worker<TypeA> worker; worker.DoWork() } ...
но здесь у работника есть только область действия в ветке if, и мне нужно, чтобы она длилась всю жизнь программы. Кроме того, соответствующие пользовательские параметры, вероятно, указали бы 4+ «политики», каждая из которых имеет несколько параметров (скажем, 4), поэтому кажется, что вы быстро столкнетесь с неприятной проблемой, когда шаблонный класс может занять 1 из 4 * 4 *. 4 * 4 комбинации шаблонов.
Кроме того, перемещение логики цикла в типы — не вариант — если бы это было связано с накладными расходами виртуальных функций, я бы использовал обычный полиморфизм. Фактическое управление циклами может быть довольно сложным и может изменяться во время выполнения.
Предполагает ли это, что я должен попытаться создать собственный итератор и передать его в качестве аргумента функции и использовать обычный полиморфизм, или это повлечет за собой аналогичные издержки?
Каков хороший дизайн для выбора классов во время выполнения, не прибегая к указателям на абстрактные базовые классы?
У вас есть классическая проблема диспетчеризации во время компиляции: «Кроме того, соответствующие параметры пользователя, вероятно, будут указывать дополнительные политики, каждая из которых имеет несколько параметров». Ваш код должен поддерживать множество комбинаций опций, которые вы не знаете во время компиляции.
Это означает, что вы должны написать некоторый код для каждой возможной комбинации, а затем отправить выбор пользователя на одну из комбинаций. Это подразумевает, что у вас должен быть какой-то уродливый и не очень эффективный фрагмент кода, где вы анализируете решения пользователя во время выполнения и отправляете их в предопределенные шаблоны.
Чтобы сохранить эффективность как можно выше, вы хотите выполнять эту диспетчеризацию на очень высоком уровне, как можно ближе к точкам входа. С другой стороны, ваш низкоуровневый код может шаблонизироваться столько, сколько вам нужно.
Это означает, что диспетчеризация может иметь несколько шагов от не-шаблонного кода к сочетанию шаблонов и опций до полной реализации.
Обычно это достигается лучше с помощью тегов и политик, а не CRTP, но это зависит от ваших алгоритмов и параметров.
Других решений пока нет …