我经常在我的项目中使用“依赖注入”模式 . 在C中,最简单的方法是通过传递原始指针来实现,但现在使用C 11,高级代码中的所有内容都应该可以使用智能指针 . 但这个案例的最佳做法是什么?性能并不重要,干净且易懂的代码对我来说更重要 .
让我举一个简化的例子 . 我们有一个使用里面距离计算的算法 . 我们希望能够用不同的距离度量(Euclidean,Manhattan等)替换此计算 . 我们的目标是能够说出类似的话:
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;
}
};
让's assume that copying is not really an issue, and if we didn' t需要多态,我们只需将 DistanceCalculator
传递给 SomeAlgorithm
的构造函数(复制) . 但由于我们需要能够传入不同的派生实例(不进行切片),因此参数必须是原始指针,引用或智能指针 .
我想到的一个解决方案是通过reference-to-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)?
这似乎是一个非常标准的问题,必须有一些简洁的方法来实现它 .
3 回答
如果我想这样做,我要做的第一件事是杀死你的界面,而是使用它:
类型擦除调用对象 .
我可以像你这样使用你的_2560869进行直接替换:
但由于距离计算器很少需要状态,我们可以取消对象 .
在C 1y的支持下,这缩短为:
因为它不再需要局部变量,可以内联使用:
但同样,
EuclideanDistance
没有任何真实的状态,所以相反我们可以如果我们确实不需要移动但我们确实需要状态,我们可以编写一个不支持基于非移动的赋值的
unique_function< R(Args...) >
类型,并存储其中一个 .其核心是接口
DistanceCalculator
是噪声 . 变量的名称通常就足够了 .std::function< double(Point,Point) > m_DistanceCalculator
明确了它的作用 . 类型擦除对象std::function
的创建者处理任何生命周期管理问题,我们只按值存储function
对象 .如果你的实际依赖注入更复杂(比如多个不同的相关回调),使用接口也不错 . 如果你想避免复制要求,我会这样做:
然后,写下你自己的
make_unique<T>(Args&&...)
(一个std
将在C 1y中出现),并像这样使用它:接口:
使用:
如果您不想
virtual ~InterfaceForDependencyStuff()
并且想要使用unique_ptr
,则必须使用存储其删除器的unique_ptr
(通过传入有状态删除器) .另一方面,如果
std::shared_ptr
已经附带make_shared
, and 默认情况下会将其删除状态存储起来 . 因此,如果你使用界面的shared_ptr
存储,你会得到:和
和
make_shared
将存储一个删除ImplementationForDependencyStuff
的函数指针,该函数在将其转换为std::shared_ptr<InterfaceForDependencyStuff>
时不会丢失,因此您可以在InterfaceForDependencyStuff
中安全地缺少virtual
析构函数 . 我个人不会打扰,并留下virtual ~InterfaceForDependencyStuff
.在大多数情况下,您不需要或不需要所有权转移,这会使代码更难理解,灵活性也会降低(移动对象无法重复使用) . 典型的情况是保持呼叫者的所有权:
原始指针可以表达非所有权 . 如果您愿意,可以在构造函数参数中使用引用,它没有任何实际区别 . 但是,不要使用引用作为数据成员,它会使类不必要地无法分配 .
只使用任何指针的缺点(智能或原始),甚至是普通的C引用,它们允许从const上下文中调用非const方法 .
对于没有问题的单个方法的无状态类,
std::function
是一个很好的选择,但是对于具有状态或多个方法的类的一般情况,我提出了一个与std::reference_wrapper
(缺少const安全访问器)相似但不相同的包装器) .用法:
将
T*
替换为unique_ptr
或shared_ptr
以获取拥有版本 . 在这种情况下,还可以添加任何unique_ptr<T2>
或shared_ptr<T2>
的移动构造和构造 .