如何返回两条线相交的点?并不是所有的线都会相交。

6
假设我们想制作一个函数,用于计算两条直线的交点。交点并不总是被定义或唯一的。如何在函数签名中反映这一点?
我想到了以下几种选择:
1. bool getIntersectionPoint(Line& a, Line& b, Point& result); 如果直线平行则返回false。否则返回true,并将结果写入变量。
2. Point getIntersectionPoint(Line& a, Line& b); 如果直线平行,则抛出异常。
[更新] 如果我们制作两个函数bool doLinesIntersect(const Line&, const Line&)和Point twoLinesIntersection(const Line&, const Line&),第二个函数仍然可以在第一个函数返回false后调用。

3
我非常怀疑你想针对这个抛出异常。 - Rob
3
我认为抛出异常是错误的 - 因为两条平行线没有交点不是一个“错误”,而是一种正常结果。 - CBroe
3
没有“最好”的方式。两种方法都有其可取之处(特别是在没有上下文的情况下)。 - NPE
2
确保你不要返回一个指向本地 Point 的引用! - chrisw
2
如果你选择第一个解决方案(我更喜欢的那个),你的方法应该被命名为 calculateIntersectionPoint 或类似的名称。还有第三种可能性:返回一个特殊值,表示不存在交点。在Java中,你可以返回null。在C++中,这可以是一个std::pair<bool,Point>(我不会返回一个指针)。 - Axel
显示剩余7条评论
7个回答

4
我认为,线段相交会产生对象,因此诚实地说应该有以下内容: boost::variant<Empty, Point, Line> intersect(Line const & l1, Line const & l2) 以及帮助函数,如 boost::optional<Point> getIntersectionPoint(Line const & l1, Line const & l2) bool isParallel(Line const & l1, Line const & l2) 编辑: 如果您不想使用Boost库,可以轻松创建简单的类比。
struct intersection_result_t
{
  enum isec_t
  {
    isec_empty, isec_point, isec_line
  }

  intersection_result_t()
    : type_(isec_empty)
  {
    new (storage_) Empty();
  }

  intersection_result_t(Empty const & e)
    : type_(isec_empty)
  {
    new (storage_) Empty(e);
  }
  intersection_result_t(Point const & p)
    : type_(isec_point)
  {
    new (storage_) Point(p);
  }
...
  intersection_result_t(intersection_result_t & ir)
    : type_(ir.type_)
  {
    switch(ir.type_)
    {
      case isec_empty:
        new (storage_) Empty(*static_cast<Empty*>(ir.storage_));
      case ....
    }
  }
private:
  void destroy()
  {
    switch(type_)
    {
      case isec_empty:
        operator delete (static_cast<Empty*>(storage_), storage_);
      case ....
    }
  }
private:
  char storage_[MAX(sizeof(Empty), sizeof(Point), sizeof(Line))];
  isec_t type_;
};

等等,还需要一些开关。或者您可以使用模板。 对于可选项,请改用initialized_而不是type_来跟踪构造状态。


你能否轻松地做到这样的事情,而不使用boost库? - Dennis
编辑不够充分。不能保证storage_会正确对齐。请使用std::aligned_storage - Mankarse

3

正如ulidtko所建议的那样,返回一个“可能是点”的对象会很好。在C++中,您可以使用boost::optional

boost::optional<Point> getIntersectionPoint(const Line& a, const Line& b) {
    // ...
    if (there_is_zero_or_inifinty_points_of_intersection)
        return boost::optional<Point>();
    else
        return boost::optional<Point>(the_point_of_intersection);
}

您可以将boost::optional<Point>视为Point*。特别地,客户端可以通过以下方式查询返回的交点是否是一个适当的点:

boost::optional<Point> point = getIntersectionPoint(a, b);
if (point)
    // point "points to" a proper Point which can be retrieved as *point
else
    // point is "NULL", that is, there's no unique point of intersection

有趣的是,boost::optional 的激励案例也是一个几何问题。这不是巧合,因为我相信 boost::optional 的作者编写几何软件。;-)
值得一提的是,有一份提议在C ++标准的下一个版本中将optional包含到STL中。

很遗憾的是,你必须在每个使用这种值的地方都加上 if () {} else {},这可能会有点烦人。由于C++中没有单子,抛出异常的方法在某些情况下可能会因其代码清晰度而胜出。 - ulidtko

0

并行线不是一个错误或意外情况。因此,抛出异常是不合适的。

顺便说一下,这更适合作为函数签名。

bool getIntersectionPoint(const Line& a, const Line& b, Point& result);

指定常量可以清楚地表明函数不会修改其前两个参数,并且还允许您使用临时变量调用函数。

4
这个方法名叫做 getIntersectionPoint(),所以不能计算交点(因为直线平行)可能被视为错误。 - Andreas Fester
平行线不相交是众所周知的。因此,我认为任何调用 getIntersectionPoint 的人都会明白他们必须注意这一点,并且该函数不提供任何保证会找到交点。 - john
公平地说,原始问题调用了getIntersectionPoint函数,因此基于这一点对其进行评分有些不公平。 - Pete

0

从抽象(API)的角度来看,您有两个不相关的函数:

bool doLinesIntersect(const Line&, const Line&);

并且

Point twoLinesIntersection(const Line&, const Line&);

第二个函数必须假设这些线确实相交(而不是共线)。如果你不信任调用者,你可能想要抛出一个异常,指示前提条件没有满足。

3
这确实是一种可行的第三种方式。它有一些优点。然而,缺点是你可能最终会做两次相同的工作。 - NPE
1
我认为,这种方法不好。如果twoLinesIntersection找不到结果会怎么样?例如,在doLinesIntersect返回false之后,我仍然调用它。 - Kolyunya

0

你的第二个函数可能不应该返回一个Point&,而是返回一个Point值(谁拥有它?)

或者,还有第三种选择:

Point getIntersectionPoint ( Line& a, Line& b, bool* ok );

如果您为'ok'提供了一个NULL指针,那么如果没有交集,将会抛出异常,否则在'ok'的值中返回false。

我建议对于这样的函数,最好完全避免使用异常。非交集并不是真正异常的情况,异常应该被保留给那些意外的情况。您可以预料到非相交线。

使用返回bool值的版本,或者带有bool参数的版本,但不要抛出异常。

编辑 经常使用的第四种选择:

std::pair<bool, Point> getIntersectionPoint ( Line& a, Line& b );

0

这个问题对于在C++中更容易实现sum types非常有帮助。

在像Haskell这样的语言中,您的函数会有以下签名:

getIntersectionPoint :: Line -> Line -> Maybe Point

Maybe Point(函数的返回类型)本质上意味着一种类型可以有两个值:NothingJust p,其中p是一个Point

这样的简单和类型的可用性实际上使得问题根本不必要,因为所有方法都会合并成一个。


编辑:这个答案很好地展示了Boost提供的简单的和易用的求和类型工具。有boost::optionalboost::variant。太棒了。


-1

没有给出上下文,人们会无休止地讨论。

假设你想在某些地方使用这个函数

fillWithColor(color c, set_of lines& figure);

你可以使用getLinesIntersection来实现这个功能。如果你需要检查每个调用,不仅会让你的代码变得混乱,而且你也不知道如何处理错误。简单地使用该函数,让调用者捕获异常。

在其他情况下,你可以实现:

bool doLinesIntersect(const Line&, const Line2&, Point &p);
Point getLinesIntersection(const Line&, const Line2&)
{
   Point p;
   If (! doLinesIntersect(Line, Line2,p) throw …;
   return p;
}

两种方法都非常有效!!!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接