Использование шаблона вместо переключателя

Я хочу выполнить набор тестов в моем коде, которые похожи, но изменяются только в зависимости от параметра.

Я мог бы написать это с помощью оператора switch:

bool doTest(EnumSensorFamily family, const StructSensorProposal& proposed)
{
  switch (family)
  {
  case FAM1:
    return (ExpectedFam1 == proposed.Fam1SensorId);
    break;
  case FAM2:
    return (ExpectedFam2 == proposed.Fam2SensorId);
    break;
  case FAM3:
    return (ExpectedFam3 == proposed.Fam3SensorId);
    break;
  default:
    ERROR ("Unexpected family");
    return false;
  }
}

Я думал об этом с помощью специализированных шаблонов

template <EnumSensorFamily family>
bool doTest(const StructSensorProposal& proposed);

template<>
bool doTest<FAM1> (const StructSensorProposal& proposed)
{
  return (ExpectedFam1 == proposed.Fam1SensorId);
}

template<>
bool doTest<FAM2> (const StructSensorProposal& proposed)
{
  return (ExpectedFam2 == proposed.Fam2SensorId);
}

template<>
bool doTest<FAM3> (const StructSensorProposal& proposed)
{
  return (ExpectedFam3 == proposed.Fam3SensorId);
}

Есть ли какая-либо польза от этого, кроме исключения оператора switch, содержащего почти идентичные случаи?

В идеале я хотел бы иметь возможность написать один метод для сокращения расходов на обслуживание.

спасибо

Ответ 1

Построение ответа Эндрю...

Обратите внимание, что EnumSensorFamily family должен быть известен во время компиляции. Если это неизвестно до времени выполнения, вам нужно написать switch, чтобы выбрать шаблон, вернув вас туда, где вы начали.

Другой способ сделать это - с шаблоном "Черты":

template <EnumSensorFamily family>
struct SensorTraits;

template <>
struct SensorTraits<FAM1>
{
    const EnumSensorFamily kFamilyID = ExpectedFam1;
};

template <>
struct SensorTraits<FAM2>
{
    const EnumSensorFamily kFamilyID = ExpectedFam2;
};

template <>
struct SensorTraits<FAM3>
{
    const EnumSensorFamily kFamilyID = ExpectedFam3;
};

template <EnumSensorFamily family>
bool doTest(const StructSensorProposal& proposed)
{
  return (SensorTraits<family>::kFamilyID == proposed.Fam1SensorId);
}

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

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

РЕДАКТИРОВАТЬ: Вы можете сделать поле зависимым от семейства датчиков с указателем на элемент:

template <>
struct SensorTraits<FAM1>
{
    const EnumSensorFamily kFamilyID = ExpectedFam1;
    int StructSensorProposal::*proposalField = &StructSensorProposal::fam1field;
};

// ...

template <EnumSensorFamily family>
int getProposedField(const StructSensorProposal& proposed)
{
    return proposed.*SensorTraits<family>::proposalField;
}

Вы также можете ввести, например, typedef для типа данных датчика:

template <>
struct SensorTraits<FAM1>
{
    const EnumSensorFamily kFamilyID = ExpectedFam1;
    typedef uint16_t data_type;
    data_type StructSensorProposal::*proposalField = &StructSensorProposal::fam1field;
};

// ...

template <EnumSensorFamily family>
SensorTraits<family>::data_type getProposedField(const StructSensorProposal& proposed)
{
    return proposed.*SensorTraits<family>::proposalField;
}

Я не тестировал их; вам может понадобиться const или static.

Ответ 2

Преимущество - это очень небольшое потенциальное повышение производительности, если компилятор не смог правильно оптимизировать switch (то есть, если он не генерирует тот же код, что и для решения шаблона, что можно сделать для современного встроенного компилятора), Конечно, только если family является константой времени компиляции - в противном случае шаблоны неприменимы, и лучшая оптимизация, которую может найти компилятор для switch, будет вычисленным скачком или таблицей переходов.

Если ваш код всегда выглядит как return (ExpectedFamN == proposed.FamNSensorId);, я предпочитаю использовать массивы для ожидаемых значений и идентификаторов датчиков и индексировать их на основе family.

Ответ 3

Невозможно использовать шаблоны в следующем случае:

const  EnumSensorFamily familyCompileTime = FAM3; // Compile time constant
EnumSensorFamily family = GetFimilyInRunTime(); // Run time variable
doTest1(family, proposed); // ok
doTest2<family>(proposed); // error;
doTest2<familyCompileTime >(proposed); // OK;

Ответ 4

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

Кроме того, это приведет к созданию более чистого кода.

Ответ 5

Существует также преимущество в двоичном размере, только исполняемые шаблоны будут исполняться. Если у вас огромный переключатель, это может быть значительным для размера exe! ​​