Разница между выбором подкласса с помощью параметра шаблона или созданием экземпляра переменной-члена

#c

#c

Вопрос:

Допустим, у меня есть класс, который может использовать различные типы функций расстояния (евклидово расстояние и т.д.) Для выполнения некоторых сравнений. Я реализовал эти функции как подклассы класса Distance:

 class Distance;
class EuclideanDistance : public Distance;
class OtherDistance : public Distance;
  

Кажется, что для выбора типа расстояния для использования я мог бы сделать:

 template <typename TDistance>
class MyClass;
  

и создайте его с помощью:

 MyClass<EuclideanDistance> instance;
  

или выполните то же самое с:

 class MyClass
{
public:
 Distance* myDistanceFunction;
}
  

и созданием его экземпляра с помощью

 MyClass instance;
instance.myDistanceFunction = new EuclideanDistance;
  

есть ли какие-либо преимущества одного метода перед другим?

Спасибо,

Дэвид

Ответ №1:

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

Кроме того, классы, созданные на основе шаблона, параметризованного разными типами (реализации distance), будут считаться разными типами и не будут взаимозаменяемыми: MyClass<EuclideanDistance> это другой тип, чем MyClass<MinkowskiDistance> . Это заставит вас создавать все функции, которые также работают с MyClass шаблонами, и в конечном итоге приведет к большей сложности без каких-либо дополнительных преимуществ.

Шаблоны следует использовать, когда вам нужно ослабить типобезопасность языка, например, когда вы пишете класс, который должен работать с несколькими несвязанными типами (не производными от общего базового класса / интерфейса), которые, тем не менее, ведут себя аналогичным образом (например, у всех есть kwak() функция-член). Это называется утиным вводом: типы формально не связаны, но все обладают схожими свойствами.

В случае, если вы не можете гарантировать, что все реализации distance являются производными от общего базового класса / интерфейса, вам может потребоваться использовать шаблоны. В противном случае предпочитайте простое и гибкое объединение.

Ответ №2:

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

Комментарии:

1. Я вижу — так что я бы просто хотел, чтобы EuclideanDistance и OtherDistance были отдельными классами, не производными от какого-либо родительского класса Distance.

2. Это было бы вполне возможно 🙂 Шаблоны допускают универсальное программирование, рассматривая вещи, которые имеют один и тот же неявный интерфейс (вещи, требуемые шаблоном), как одинаковые. Это та же концепция, что и то, как STL часто позволяет передавать функтор (функциональный объект) или фактическую функцию — шаблону, использующему переданную функцию или функтор, все равно, какой это, ему просто важно, чтобы переданный параметр вызывался с синтаксисом (). Таким образом, шаблоны намного более гибкие, чем иерархия наследования (но у обоих определенно есть плюсы и минусы).

Ответ №3:

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

Комментарии:

1. Во втором методе — есть ли способ создать второй объект того же типа, что и первый?

2. @DavidDoria: какой объект вы имеете в виду? A MyClass , a EuclideanDistance ?

3. Другой объект того же типа, что и myDistanceFunction внутри MyClass.

4. @DavidDoria: Если вы создадите a typename TDistance внутри a MyClass<EuclideanDistance> , то он станет EuclideanDistance .

5. Конечно, но как насчет другого случая — не шаблонного класса. Не могли бы вы использовать функцию typeof() или что-то подобное, чтобы создать второй экземпляр функции расстояния того же типа, что и «основной» объект функции расстояния в классе?

Ответ №4:

Общедоступное наследование означает -A. Возможно ли взаимодействие EuclideanDisatance с «LorentzDistance» или «QuaternionDistance» (или любым другим) каким-либо значимым образом? Являются ли они обоими «расстояниями» в том смысле, что какой-то код где-то был бы рад использовать любой из них нетривиальным способом (то есть чем-то другим, кроме печати значения и т.д.)? Я предполагаю, что нет, это не так.

Итак, вам нужны обобщения, а не наследование. Шаблоны — это более простое обещание: две вещи синтаксически «похожи» друг на друга.