6

假设我们要创建一个计算两条线的交点的函数。交点并不总是被定义或唯一的。如何在函数的签名中反映这一点?

我想出了这些选项:

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

    如果线平行,则返回 false。否则返回 true 并将结果写入变量。

  2. Point getIntersectionPoint ( Line& a, Line& b );

    如果线平行,则抛出异常。

[更新]
如果我们创建 2 个函数bool doLinesIntersect(const Line&, const Line&);,并且Point twoLinesIntersection(const Line&, const Line&);在第一个返回 false 后仍然可以调用第二个。

4

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_跟踪构造状态。

于 2013-03-26T10:03:54.240 回答
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++ 标准的下一个修订版中,有一个提议包含在 STL 中。optional

于 2013-03-26T10:25:12.143 回答
0

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

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

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

第二个函数必须假设这些线确实相交(并且不共线)。如果你不信任你的调用者,你可能想抛出一个异常,表明不满足先决条件。

于 2013-03-26T09:47:30.750 回答
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 );
于 2013-03-26T09:50:46.887 回答
0

平行线不是错误,也不是意外。因此抛出异常是不合适的。

顺便说一句,这作为函数签名更可取。

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

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

于 2013-03-26T09:44:03.540 回答
0

这个问题是C++中更简单的求和类型的一个很好的动机。

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

getIntersectionPoint :: Line -> Line -> Maybe Point

where Maybe Point(函数的返回类型)本质上意味着一个可以有两个值的类型:Nothing或者Just p,其中p是 a Point

这种简单求和类型的可用性实际上会使这个问题变得毫无意义,因为所有方法都会合并为一个单一的方法。


编辑:这个答案巧妙地证明了 Boost 提供了简单的求和类型工具。有boost::optionalboost::variant。甜的。

于 2013-03-26T09:58:00.517 回答
-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;
}

两种方法都非常有效!

于 2013-03-26T10:21:46.683 回答