Я часто использую шаблон "зависимость" в моих проектах. В С++ его проще всего реализовать, перейдя по необработанным указателям, но теперь с С++ 11 все в высокоуровневом коде должно выполняться с помощью интеллектуальных указателей. Но какова наилучшая практика для этого случая? Производительность не имеет решающего значения, теперь чистый и понятный код имеет значение для меня.
Позвольте мне показать упрощенный пример. У нас есть алгоритм, который использует вычисления расстояния внутри. Мы хотим иметь возможность заменить этот расчет на разные метрики расстояния (Евклида, Манхэттен и т.д.). Наша цель - сказать что-то вроде:
SomeAlgorithm algorithmWithEuclidean(new EuclideanDistanceCalculator());
SomeAlgorithm algorithmWithManhattan(new ManhattanDistanceCalculator());
но с умными указателями, чтобы избежать ручных new
и delete
.
Это возможная реализация с исходными указателями:
class DistanceCalculator {
public:
virtual double distance(Point p1, Point p2) = 0;
};
class EuclideanDistanceCalculator {
public:
virtual double distance(Point p1, Point p2) {
return sqrt(...);
}
};
class ManhattanDistanceCalculator {
public:
virtual double distance(Point p1, Point p2) {
return ...;
}
};
class SomeAlgorithm {
DistanceCalculator* distanceCalculator;
public:
SomeAlgorithm(DistanceCalculator* distanceCalculator_)
: distanceCalculator(distanceCalculator_) {}
double calculateComplicated() {
...
double dist = distanceCalculator->distance(p1, p2);
...
}
~SomeAlgorithm(){
delete distanceCalculator;
}
};
Предположим, что копирование на самом деле не является проблемой, и если нам не нужен полиморфизм, мы просто передадим DistanceCalculator
конструктору SomeAlgorithm
по значению (копирование). Но так как мы должны иметь возможность проходить в разных производных экземплярах (без нарезки), параметр должен быть либо необработанным указателем, либо ссылкой, либо умным указателем.
Одно из решений, которое приходит на ум, - передать его ссылкой-на-const и инкапсулировать его в переменную-член std::unique_ptr<DistanceCalculator>
. Тогда вызов будет:
SomeAlgorithm algorithmWithEuclidean(EuclideanDistance());
Но этот временный объект, распределенный по стеку (rvalue-reference?), будет разрушен после этой строки. Поэтому нам нужно некоторое копирование, чтобы сделать его более похожим на пропускную стоимость. Но поскольку мы не знаем тип среды выполнения, мы не можем легко создать нашу копию.
Мы могли бы также использовать интеллектуальный указатель как параметр конструктора. Поскольку нет права собственности (DistanceCalculator
будет принадлежать SomeAlgorithm
), мы должны использовать std::unique_ptr
. Должен ли я заменить все такие параметры конструктора на unique_ptr
? это уменьшает читаемость. Также пользователь SomeAlgorithm
должен построить его неловко:
SomeAlgorithm algorithmWithEuclidean(std::unique_ptr<DistanceCalculator>(new EuclideanDistance()));
Или я должен каким-то образом использовать новую семантику перемещения (& &, std:: move)?
Кажется, это довольно стандартная проблема, должен быть какой-то краткий способ ее реализации.