Дизайн и стратегии полиморфизма во время выполнения с CRTP

В моей работе много циклов с множеством внутренних вызовов функций; здесь важна производительность, а накладные расходы на вызовы виртуальных функций неприемлемы, поэтому я стараюсь избегать динамического полиморфизма, используя 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() } ...

но здесь worker имеет область действия только в ветке if, и мне нужно, чтобы он имел жизнь, равную длине программы. Кроме того, соответствующие пользовательские параметры, вероятно, будут указывать более 4 «политик», каждая из которых имеет несколько параметров (скажем, 4), поэтому кажется, что у вас быстро возникнет неприятная проблема, когда шаблонный класс может принять 1 из 4 * 4 * 4*4 комбинации шаблонов.

Кроме того, перенос логики цикла в типы не вариант - если бы это было, накладные расходы на вызов виртуальной функции были бы незначительными, и я бы использовал обычный полиморфизм. Фактическое управление циклами может быть довольно сложным и будет меняться во время выполнения.

Может ли это означать, что я должен попытаться создать собственный итератор и передать его в качестве аргумента функции и использовать обычный полиморфизм, или это повлечет за собой аналогичные накладные расходы?

Как лучше выбрать классы во время выполнения, не прибегая к указателям на абстрактные базовые классы?


person Aurelius    schedule 20.12.2013    source источник
comment
Теперь мне нужно, чтобы фактический тип зависел от параметра пользователя во время выполнения CRTP не является способом создания какой-либо динамической диспетчеризации. Я думаю, вы скорее ищете технику девиртуализации.   -  person dyp    schedule 21.12.2013
comment
Вы можете связать пользовательские решения с таблицей указателей функций (std::functions) вместо использования switch. Для другого варианта, если вам нужно более длительное время жизни Worker - для этого предназначена продолжительность динамического хранения (т.е. создайте Worker в куче).   -  person dyp    schedule 21.12.2013
comment
Что касается std::function, в этом ответе говорится, что такая вещь предотвратит любое встраивание, так что это тоже нежизнеспособный вариант.   -  person Aurelius    schedule 21.12.2013
comment
Я имею в виду выбрать Worker, а не для функции Func.   -  person dyp    schedule 21.12.2013
comment
Я не понимаю, как это избавит от необходимости указывать тип шаблона.   -  person Aurelius    schedule 21.12.2013
comment
Это не так. Это просто альтернатива использованию ветвления/переключателя для выбора типа/функции Worker. Поскольку вы не можете позволить себе вызов виртуальной функции (или любой косвенный вызов) для каждого вызова Func внутри цикла, вам нужно выбрать конкретный WorkLoop, где вызываемый Func известен во время компиляции. Этот выбор можно сделать с помощью переключателя/разветвления, таблицы функций, фабрики.   -  person dyp    schedule 21.12.2013


Ответы (1)


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

Это означает, что вам нужно написать некоторый код для каждой возможной комбинации, а затем отправить выбор пользователя на одну из комбинаций. Это означает, что у вас должен быть какой-то уродливый и не очень эффективный фрагмент кода, в котором вы анализируете решения пользователя во время выполнения и отправляете их в предопределенные шаблоны.

Чтобы поддерживать эффективность на максимально возможном уровне, вы должны выполнять эту диспетчеризацию на очень высоком уровне, как можно ближе к точкам входа. С другой стороны, ваш низкоуровневый код может шаблонизироваться сколько угодно.

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

Обычно это лучше достигается с помощью тегов и политик, а не CRTP, но это сильно зависит от ваших алгоритмов и опций.

person Michael Simbirsky    schedule 21.12.2013