Это зависит от Ваших целей. Можно использовать версию 1, если Вы
, я обычно предпочитал бы версию 1 с помощью полиморфизма во время выполнения, потому что это все еще гибко и позволяет, у Вас, чтобы иметь Автомобиль все еще есть тот же тип: Car<Opel>
другой тип, чем Car<Nissan>
. Если Вашими целями является высокая эффективность при использовании тормозов часто, я рекомендую Вам использовать шаблонный подход. Между прочим, это называют основанным на политике дизайном. Вы обеспечиваете политика тормоза . Пример, потому что Вы сказали Вас, запрограммировал в Java, возможно Вы слишком еще не испытаны с C++. Один способ сделать его:
template<typename Accelerator, typename Brakes>
class Car {
Accelerator accelerator;
Brakes brakes;
public:
void brake() {
brakes.brake();
}
}
, Если у Вас есть много политик, можно собрать в группу их в их собственную структуру и передачу что один, например, как SpeedConfiguration
сбор Accelerator
, Brakes
и еще немного. В моих проектах я пытаюсь сохранить много кода без шаблонов, позволяя им быть скомпилированным однажды в их собственные объектные файлы, не нуждаясь в их коде в заголовках, но все еще позволяя полиморфизм (через виртуальные функции). Например, Вы могли бы хотеть сохранить общие данные и функции, что нешаблон кода, вероятно, обратится ко многим случаям в базовом классе:
class VehicleBase {
protected:
std::string model;
std::string manufacturer;
// ...
public:
~VehicleBase() { }
virtual bool checkHealth() = 0;
};
template<typename Accelerator, typename Breaks>
class Car : public VehicleBase {
Accelerator accelerator;
Breaks breaks;
// ...
virtual bool checkHealth() { ... }
};
Кстати, который является также подходом, который используют потоки C++: std::ios_base
содержит флаги, и наполните, которые не зависят от символьного типа или черт как openmode, флаги формата и материал, в то время как std::basic_ios
тогда шаблон класса, который наследовал его. Это также уменьшает чрезмерное увеличение размера кода путем совместного использования кода, который характерен для всех инстанцирований шаблона класса.
Частного наследования нужно избежать в целом. Это только очень редко полезно, и включение является лучшей идеей в большинстве случаев. Общий падеж, где противоположное верно, когда размер действительно крайне важен (основанный на политике строковый класс, например): Пустая Оптимизация Базового класса может применяться при получении из пустого класса политики (просто содержащий функции).
Использование Read и злоупотребления Наследованием Herb Sutter.
Эмпирическое правило:
1), Если выбор конкретного типа сделан во время компиляции, предпочтите шаблон. Это будет более безопасно (ошибки времени компиляции по сравнению с ошибками периода выполнения) и вероятно лучше оптимизированный. 2), Если выбор сделан во времени выполнения (т.е. в результате действия пользователя) нет действительно никакого выбора - наследование использования и виртуальные функции.
Шаблоны являются способом позволить классу использовать переменную, о которой Вы действительно не заботитесь о типе. Наследование является способом определить то, что класс основан на своих атрибутах. Это , "-" по сравнению с, "имеет -" вопрос.
Другие опции:
Используйте интерфейс, если Вы предполагаете для поддержки различных классов Повреждения и его иерархии сразу.
Car( new Brake() )
Car( new BrakeABC() )
Car( new CoolBrake() )
И Вы не знаете эту информацию во время компиляции.
, Если Вы знаете, какое Повреждение Вы собираетесь использовать 2b, правильный выбор для Вас определить различные Автомобильные классы. Тормоз в этом случае будет Вашим автомобилем "Стратегия", и можно установить по умолчанию.
я не использовал бы 2a. Вместо этого можно добавить статические методы Повредить и назвать их без экземпляра.
Абстрактный базовый класс имеет на издержках виртуальных вызовов, но это имеет преимущество, что все производные классы являются действительно базовыми классами. Не, итак, когда Вы используете шаблоны †“Car< Brake> и Car< BrakeWithABS> не связаны друг с другом, и Вы будете иметь или к dynamic_cast и проверке на пустой указатель или иметь шаблоны для всего кода, который имеет дело с Автомобилем.
На большую часть Вашего вопроса уже ответили, но я хотел уточнить этот бит:
Происходя из Java, я естественно склонен всегда использовать версию 1, но шаблонные версии, кажется, часто предпочитаются, например, в коде STL? Если это правда, это только из-за эффективности памяти и т.д. (никакое наследование, никакие вызовы виртуальной функции)?
Это - часть его. Но другим фактором является добавленная безопасность типов. Когда Вы рассматриваете a BrakeWithABS
как a Brake
, Вы теряете информацию о типе. Вы больше не знаете, что объект на самом деле a BrakeWithABS
. Если это - шаблонный параметр, Вы имеете в наличии точный тип, который в некоторых случаях может позволить компилятору работать лучше typechecking. Или это может быть полезно в обеспечении, что корректную перегрузку функции называют. (если stopCar()
передает объект Тормоза второй функции, которая может иметь отдельную перегрузку для BrakeWithABS
, это не назовут при использовании наследования и Вашего BrakeWithABS
был брошен к a Brake
.
Другой фактор - то, что это позволяет больше гибкости. Почему все Тормозят, реализации должны наследоваться тому же базовому классу? Базовый класс на самом деле имеет что-нибудь для обеспечения к таблице? Если я пишу класс, который выставляет ожидаемые функции членства, разве который не достаточно хорош для действия как тормоз? Часто, явно использующие интерфейсы или абстрактные базовые классы ограничивают Ваш более, чем необходимый код.
(Отметьте, я не говорю, что шаблоны должны всегда быть предпочтительным решением. Существуют другие проблемы, которые могли бы влиять на это, в пределах от скорости компиляции к, "какие программисты в моей команде знакомы с" или просто, "что я предпочитаю". И иногда, Вам нужен полиморфизм во время выполнения, в этом случае шаблонное решение просто не возможно),
этот ответ более или менее корректен. Когда Вы хотите что-то параметризованное во время компиляции - необходимо предпочесть шаблоны. Когда Вы хотите что-то параметризованное во времени выполнения, необходимо предпочесть переопределяемые виртуальные функции.
Однако использование шаблонов не устраняет Вас от выполнения обоих (создание шаблонной более гибкой версии):
struct Brake {
virtual void stopCar() = 0;
};
struct BrakeChooser {
BrakeChooser(Brake *brake) : brake(brake) {}
void stopCar() { brake->stopCar(); }
Brake *brake;
};
template<class Brake>
struct Car
{
Car(Brake brake = Brake()) : brake(brake) {}
void slamTheBrakePedal() { brake.stopCar(); }
Brake brake;
};
// instantiation
Car<BrakeChooser> car(BrakeChooser(new AntiLockBrakes()));
Однако я, вероятно, НЕ использовал бы шаблоны для этого... Но его действительно просто персональный вкус.
Лично я всегда предпочел бы использовать интерфейсы поверх шаблонов по нескольким причинам:
Я использую шаблоны только тогда, когда виртуальные таблицы создают какие-то издержки.
Конечно, это только мое мнение.