没有原始指针的C++ 11中的依赖注入

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)?

这似乎是一个非常标准的问题,必须有一些简洁的方法来实现它.

Yak*_*ont 9

如果我想这样做,我要做的第一件事是杀死你的界面,而是使用它:

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那里.


Phi*_*ipp 5

在大多数情况下,您不希望或不需要所有权转移,这会使代码更难以理解且灵活性较差(移出的对象无法重用)。典型的情况是将所有权保留给调用者:

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)

原始指针可以很好地表达非所有权。如果您愿意,可以在构造函数参数中使用引用,这没有什么实际区别。但是,不要使用引用作为数据成员,它会使类不必要地不可分配。