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

Во многих ситуациях вопрос даже не спрашивает себя, поскольку иногда наследование предоставляет необходимые функции, которые не могут предоставить шаблоны. Например, когда мне нужно обращаться к различным типам через один базовый тип (полиморфизм), мне нужно использовать наследование.

Однако есть случаи, когда проблема может быть решена как с наследованием, так и с шаблонами.

Возьмем, например, стратегическую модель-параметризацию некоторых частей кода:

Одно решение файлового парсера может выглядеть так:

class FileParser
{
   //...
   public:
      void Parse(ParsingAlgorithm* p);
   //...
}

void FileParser::Parse(ParsingAlgorithm* p)
{
   m_whatevertypeofvaluesineed = p->Parse(whateverparametersyouneed);
}

где ParsingAlgorithm является абстрактным базовым классом, который предоставляет некоторые базовые методы и должен быть унаследован тем, кто любит реализовывать определенный парсер для класса FileParser.

Однако то же самое можно легко достичь с помощью шаблонов:

template <class Parser>
class FileParser
{
   //...
   public:
      void Parse()
      {
           m_whatevertypeofvaluesineed = m_parser.Parse(whateverparametersyouneed);
      }

   private:
      Parser m_parser;
   //...
}

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

Ответы

Ответ 1

Если во время компиляции известно, какие объекты вы собираетесь манипулировать, то статический полиморфизм с шаблонами часто является самым быстрым способом, и он создает код, который немного более краток (нет необходимости в явном наследовании). Он также может быть более общим, поскольку вы не ограничены сильной иерархией классов.

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

Мое собственное мнение:

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

Ответ 2

Шаблоны предоставляют полиморфизм времени компиляции в отличие от полиморфизма во время выполнения, обеспечиваемого наследованием. Я предпочитаю использовать шаблоны, когда могу.

В этой статье Шаблоны и Наследование подробно объясняется.

Ответ 3

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

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

Ответ 4

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

    Stream stream = open(filepath);

    Image image = ParseBMP(stream);
    // ...or
    Image image = ParseJPG(stream);

Не нужно делать вещи более сложными, чем они есть.