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