Я хочу иметь интерфейс с несколькими возможными реализациями, выбранными во время компиляции. Я видел, что CRTP — идиома выбора для реализации этого. Это почему? Альтернативой является шаблон «Стратегия», но я нигде не упоминаю эту технику:
template <class Impl>
class StrategyInterface
{
public:
void Interface() { impl.Implementation(); }
void BrokenInterface() { impl.BrokenImplementation(); }
private:
Impl impl;
};
class StrategyImplementation
{
public:
void Implementation() {}
};
template <class Impl>
class CrtpInterface
{
public:
void Interface() { static_cast<Impl*>(this)->Implementation(); }
void BrokenInterface() { static_cast<Impl*>(this)->BrokenImplementation(); }
};
class CrtpImplementation : public CrtpInterface<CrtpImplementation>
{
public:
void Implementation() {}
};
StrategyInterface<StrategyImplementation> str;
CrtpImplementation crtp;
BrokenInterface
к сожалению, в любом случае компилятор не перехватывает его, если только я не попытаюсь его использовать. Вариант «Стратегия» кажется мне лучше, так как избегает уродливого static_cast
и он использует композицию вместо наследования. Есть ли что-то еще, что позволяет CRTP, а стратегия — нет? Почему CRTP преимущественно используется вместо?
Обычная реализация шаблона стратегии точно такая же, как ваша реализация CRTP. Базовый класс определяет некоторый алгоритм, выпуская некоторые части, которые реализованы в производных классах.
Таким образом, CRTP реализует шаблон стратегии. Ваш StrategyInterface просто делегирует реализацию деталей и не является реализацией шаблона стратегии.
В то время как обе ваши реализации достигают одного и того же эффекта, я бы предпочел CRTP, потому что он использовал бы преимущества возможной пустой оптимизации базового класса.
В дополнение к статическому полиморфизму, CRTP предоставляет возможность перезаписывать функции базового класса из-за того, что он использует механизм наследования.
template <class Impl>
class BaseInterface
{
void genericFunc() { some implementation; }
}
Если используется CRTP, производный класс может выбрать перезапись genericFunc () в качестве «специальной» реализации в случае, если genericFunc () не подходит. Стратегия Паттен не сможет предложить функциональность, которую приносит нормальное наследование.
Одним из преимуществ шаблона стратегии является то, что если BasedInterface необходимо использовать зависимые типы внутри Impl, это будет намного проще, чем CRTP.
template <class Impl>
class BaseInterface
{
using SomeDerivedType = typename Impl::SomeType;
}