isa*_*ndi 11 dependency-injection smart-pointers c++11
我经常在我的项目中使用"依赖注入"模式.在C++中,通过传递原始指针来实现它是最容易的,但现在使用C++ 11,高级代码中的所有内容都应该可以通过智能指针来实现.但这个案例的最佳做法是什么?性能并不重要,干净且易懂的代码对我来说更重要.
让我举一个简化的例子.我们有一个使用里面距离计算的算法.我们希望能够用不同的距离度量(Euclidean,Manhattan等)替换此计算.我们的目标是能够说出类似的话:
SomeAlgorithm algorithmWithEuclidean(new EuclideanDistanceCalculator());
SomeAlgorithm algorithmWithManhattan(new ManhattanDistanceCalculator());
Run Code Online (Sandbox Code Playgroud)
但有智能指针,以避免手动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;
}
};
Run Code Online (Sandbox Code Playgroud)
让我们假设复制不是真正的问题,如果我们不需要多态,我们只需将值传递DistanceCalculator给构造函数SomeAlgorithm(复制).但由于我们需要能够传入不同的派生实例(不进行切片),因此参数必须是原始指针,引用或智能指针.
我想到的一个解决方案是通过reference-to-const传递它并将其封装在一个std::unique_ptr<DistanceCalculator>成员变量中.那么电话会是:
SomeAlgorithm algorithmWithEuclidean(EuclideanDistance());
Run Code Online (Sandbox Code Playgroud)
但是这个堆栈分配的临时对象(rvalue-reference?)将在此行之后被破坏.因此,我们需要进行一些复制,使其更像是传值.但由于我们不知道运行时类型,因此我们无法轻松构建我们的副本.
我们还可以使用智能指针作为构造函数参数.由于所有权(DistanceCalculator将由所有者拥有SomeAlgorithm)没有问题,我们应该使用std::unique_ptr.我真的应该用所有这些构造函数参数替换unique_ptr吗?它似乎降低了可读性.用户也SomeAlgorithm必须以尴尬的方式构建它:
SomeAlgorithm algorithmWithEuclidean(std::unique_ptr<DistanceCalculator>(new EuclideanDistance()));
Run Code Online (Sandbox Code Playgroud)
或者我应该以某种方式使用新的移动语义(&&,std :: move)?
这似乎是一个非常标准的问题,必须有一些简洁的方法来实现它.
如果我想这样做,我要做的第一件事是杀死你的界面,而是使用它:
SomeAlgorithm(std::function<double(Point,Point)> distanceCalculator_)
Run Code Online (Sandbox Code Playgroud)
类型擦除调用对象.
我可以使用你EuclideanDistanceCalculator喜欢这样的替换:
std::function<double(Point,Point)> UseEuclidean() {
auto obj = std::make_shared<EuclideanDistance>();
return [obj](Point a, Point b)->double {
return obj->distance( a, b );
};
}
SomeAlgorithm foo( UseEuclidean() );
Run Code Online (Sandbox Code Playgroud)
但由于距离计算器很少需要状态,我们可以取消对象.
有了C++ 1y支持,这缩短为:
std::function<double(Point,Point>> UseEuclidean() {
return [obj = std::make_shared<EuclideanDistance>()](Point a, Point b)->double {
return obj->distance( a, b );
};
}
Run Code Online (Sandbox Code Playgroud)
因为它不再需要局部变量,可以内联使用:
SomeAlgorithm foo( [obj = std::make_shared<EuclideanDistance>()](Point a, Point b)->double {
return obj->distance( a, b );
} );
Run Code Online (Sandbox Code Playgroud)
但同样,EuclideanDistance没有任何真实状态,所以相反我们可以
std::function<double(Point,Point>> EuclideanDistance() {
return [](Point a, Point b)->double {
return sqrt( (b.x-a.x)*(b.x-a.x) + (b.y-a.y)*(b.y*a.y) );
};
}
Run Code Online (Sandbox Code Playgroud)
如果我们确实不需要移动但我们确实需要状态,我们可以编写一个unique_function< R(Args...) >不支持基于非移动的赋值的类型,而是存储其中一个.
其核心是界面DistanceCalculator是噪音.变量的名称通常就足够了. std::function< double(Point,Point) > m_DistanceCalculator很清楚它的作用.type-erasure对象的创建者std::function处理任何生命周期管理问题,我们只是function按值存储对象.
如果你的实际依赖注入更复杂(比如多个不同的相关回调),使用接口也不错.如果你想避免复制要求,我会这样做:
struct InterfaceForDependencyStuff {
virtual void method1() = 0;
virtual void method2() = 0;
virtual int method3( double, char ) = 0;
virtual ~InterfaceForDependencyStuff() {}; // optional if you want to do more work later, but probably worth it
};
Run Code Online (Sandbox Code Playgroud)
然后,写下你自己的make_unique<T>(Args&&...)(std一个是C++ 1y),并像这样使用它:
接口:
SomeAlgorithm(std::unique_ptr<InterfaceForDependencyStuff> pDependencyStuff)
Run Code Online (Sandbox Code Playgroud)
使用:
SomeAlgorithm foo(std::make_unique<ImplementationForDependencyStuff>( blah blah blah ));
Run Code Online (Sandbox Code Playgroud)
如果您不想要virtual ~InterfaceForDependencyStuff()并且想要使用unique_ptr,则必须使用unique_ptr存储其删除器(通过传入有状态删除器).
在另一方面,如果std::shared_ptr已经自带了一个make_shared,和它在默认情况下有状态的存储其删除器.因此,如果你使用shared_ptr界面存储,你会得到:
SomeAlgorithm(std::shared_ptr<InterfaceForDependencyStuff> pDependencyStuff)
Run Code Online (Sandbox Code Playgroud)
和
SomeAlgorithm foo(std::make_shared<ImplementationForDependencyStuff>( blah blah blah ));
Run Code Online (Sandbox Code Playgroud)
并且make_shared将存储一个指针到函数删除ImplementationForDependencyStuff当你把它转换成一个不会被丢失std::shared_ptr<InterfaceForDependencyStuff>,所以你可以放心地缺少virtual的析构函数InterfaceForDependencyStuff.我个人不会打扰,离开virtual ~InterfaceForDependencyStuff那里.
在大多数情况下,您不希望或不需要所有权转移,这会使代码更难以理解且灵活性较差(移出的对象无法重用)。典型的情况是将所有权保留给调用者:
class SomeAlgorithm {
DistanceCalculator* distanceCalculator;
public:
explicit SomeAlgorithm(DistanceCalculator* distanceCalculator_)
: distanceCalculator(distanceCalculator_) {
if (distanceCalculator == nullptr) { abort(); }
}
double calculateComplicated() {
...
double dist = distanceCalculator->distance(p1, p2);
...
}
// Default special members are fine.
};
int main() {
EuclideanDistanceCalculator distanceCalculator;
SomeAlgorithm algorithm(&distanceCalculator);
algorithm.calculateComplicated();
}
Run Code Online (Sandbox Code Playgroud)
原始指针可以很好地表达非所有权。如果您愿意,可以在构造函数参数中使用引用,这没有什么实际区别。但是,不要使用引用作为数据成员,它会使类不必要地不可分配。
| 归档时间: |
|
| 查看次数: |
3834 次 |
| 最近记录: |