我应该使用引用成员来处理对象之间的依赖关系吗?

时间:2017-11-03 04:26:05

标签: c++ dependencies

作为C ++的初学者,我正在练习带有算法分配的C ++。一路上,我有一些问题,我很难通过。请原谅我,因为我还在学习,所以问题听起来是入门级的。

目标是在给定的三个类的矢量点中找到共线点。以下简要介绍三个类别'用途:

  1. :表示带有x和y值的点。
  2. LineSegment :表示两端有两个点的线段。
  3. 共线:包含在点矢量中找到的线段。算法的主要部分。
  4. 所以,我希望客户端代码看起来像这样:

    std::vector<Point> points;    // may become huge
    // populate points
    // ...
    
    Collinear collinear_points(points); 
    std::vector<LineSegment> segments_in_points = collinear_points.GetSegments();
    

    由于共线类依赖于某个点的矢量来相应地获得这些段,我认为它需要它作为数据成员。一直困扰着我的问题是,如果持有向量的副本持有对象外的向量的原始指针/引用。我认为智能指针在这里会有点过分。根据旧答案here,也许最好是参考,这也避免了潜在的昂贵复制?如果存在类,那么类之间存在这种依赖关系的常见做法是什么?

    如果在构建 collinear_points 后修改,则引用的数据 collinear_points 将与其包含的段不一致。是否通常将责任留给用户以确保对象的有效性取决于其他对象?有没有办法让 collinear_points 知道内容已被修改并将其置于无效状态?

2 个答案:

答案 0 :(得分:1)

从标题中回答你的实际问题:非拥有的原始指针仍然是通常的选择,主要是因为这是我们自旧C日以来一直在做的事情。当然,指针有问题,它可以是nullptr。因此,使用引用可以更清楚地传达null不是允许的值。因为我倾向于使用参考,虽然它甚至对我自己感觉有点奇怪。但总的来说,这是更好的设计决策

那就是说,我认为这里真正的问题是所有权。如果Collinear不拥有该向量,则API的用户必须确保该向量至少与关联的Collinear对象一样长。否则你将访问一个悬空指针/参考,事情往往会从那里下坡。 ;)

  

有没有办法让collinear_points知道内容已被修改并将其置于无效状态?

是的,有。 拥有一切。包括点向量段向量。遵循这种方法Collinear看起来像这样:

class Collinear {
public:
    // usings because I’m a lazy typer
    using PointsVec = std::vector<Point>;
    using SegmentsVec = std::vector<LineSegment>;

    // Take ownership of the points vector by either copying
    // or moving it into a member.
    explicit Collinear(const PointsVec& p): m_points(p) {}
    explicit Collinear(PointsVec&& p): m_points(std::move(p)) {}

    // Retain ownership of the segments vector by returning a const ref.
    const SegmentsVec& GetSegments(); // Check if you can make it const.

    // access functions for the two vectors ...
private:
    PointsVec m_points;
    SegmentsVec m_segments;
}

现在Collinear控制对点向量的访问。您必须将允许操作的函数编写为Collinear的成员。重要的是永远不要将非const指针或非const引用返回m_points,因为那样你可能会错过写访问。

段矢量类似。您提供写访问成员函数并Collinear保留所有权,这意味着它可以在必要时重新计算它,并且用户不需要关心它。根据计算的成本,您现在可以通过懒惰评估和您可以想到的每个优化来实现。

但是,有一种完全不同的设计方法。 什么都没有。 Collinear是否必须是一个类?它可能是命名空间中的一堆免费函数吗?

namespace Collinear {
    std::vector<LineSegment> GetSegments(const std::vector<Point>& points);
}

// ...

auto segments_in_points = Collinear::GetSegments(points);

这与自己的一切方法相反。以前,有完全控制权。现在您的用户具有完全控制权。另一方面,他们现在必须处理任何懒惰/优化/更新检测。

哪种方法合适是a)API设计理念和b)您的混乱情况。你的用户是什么?他们期待什么?哪种方法让他们的生活更轻松?由于这是一项任务,您可能不会拥有任何真实用户。因此,想象一群人可能想要使用您的代码并根据它来决定。或者只是使用您将有更多乐趣实施的方法。重要的是imo:选择两种方法中的一种。不要混用它们,因为这样的API不一致。这会增加混乱,降低易用性,并且更容易出错。

顺便说一下:

  

我认为智能指针在这里会有点过分。

使用智能指针不是一个过度杀伤的问题。这是一个所有权问题。如果你有一个拥有指针从不使用原始指针。 ......除非传统的API强迫你。即使这样,将它标记为拥有像gsl::owner<T>这样的透明包装也是一个好主意。

答案 1 :(得分:0)

  

一直困扰着我的问题是,它是否应该保存向量的副本或者保持对象外部向量的原始指针/引用

我认为你应该在这里保留一份vector来保持通用。你不希望你的collinear类依赖于特定的vector<Points>,而应该像创建实例时一样collinear课程的vector<Points>课程,您只需告诉它vector,它必须处理。然后,如果您更改此collinear,并且您希望collinear也可以处理此数据集(您可能不需要),您有责任告知collinear处理新数据集。如果您希望在更新vector<points>时自动更新collinear,则可以这样做,但是你必须回答一些问题,比如数据集发生变化时vector<points>的状态(这将取决于{{1}})。