【问题标题】:How to return the point of lines crossing? Lines don't always intersect如何返回线交叉点?线并不总是相交
【发布时间】:2013-03-26 09:42:27
【问题描述】:

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

我想出了这些选项:

  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 后被调用。

【问题讨论】:

  • 我非常怀疑你想为此抛出异常。
  • 我认为抛出异常是错误的——因为两条线是平行的,因此没有交叉点不是“错误”,而是正常的结果。
  • 没有“最好”的方法。两种方法都可能存在争论(特别是因为您没有提供上下文)。
  • 确保您没有返回对本地点的引用!
  • 如果您选择第一个解决方案(我更喜欢的那个),您的方法可能应该称为calculateIntersectionPoint。还有第三种可能性:返回一个特殊值,表示不存在交集。在 Java 中,您可以只返回 null。在 C++ 中,它可能是 std::pair<bool,Point>(我不会在 C++ 中返回指针)。

标签: c++ function exception return return-value


【解决方案1】:

恕我直言,线交叉产生对象,这就是为什么它会诚实

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 库,你能(轻松地)做这样的事情吗?
  • 编辑不够。无法保证storage_ 将正确对齐。使用std::aligned_storage
【解决方案2】:

正如 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&lt;Point&gt; 视为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++ 标准中,有一个 proposaloptional 包含到 STL 中。

【讨论】:

  • 遗憾的是,您必须用if () {} else {} 包围这样的值的每一次使用,这可能会有点烦人。由于我们在 C++ 中没有 monad,因此在某些情况下,抛出异常的方法可能会因其代码清晰而获胜。
【解决方案3】:

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

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

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

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

【讨论】:

  • 该方法称为getIntersectionPoint(),因此无法计算一个(因为线是平行的)可以视为错误
  • 平行线不相交是众所周知的。所以我认为任何调用 getIntersectionPoint 的人都会明白他们必须注意它们,并且该函数不提供任何保证可以找到交叉点。
  • 公平地说,最初的问题称为函数 getIntersectionPoint,因此基于此标记它有点不公平。
【解决方案4】:

您的第二个函数可能不应该返回一个 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 );

【讨论】:

    【解决方案5】:

    这个问题是在 C++ 中更容易sum types的一个很好的动机。

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

    getIntersectionPoint :: Line -> Line -> Maybe Point
    

    其中Maybe Point(函数的返回类型)本质上表示可以有两个值的类型:NothingJust p,其中pPoint

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


    编辑:this answer 巧妙地展示了 Boost 提供了简单的求和类型工具。有boost::optionalboost::variant。甜蜜。

    【讨论】:

      【解决方案6】:

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

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

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

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

      【讨论】:

      • 这当然是有效的第三种方式。它有一些好处。但是,缺点是您最终可能会执行两次相同的工作。
      • 这种方法不好恕我直言。如果找不到结果,twoLinesIntersection 会怎么做?例如。如果我在doLinesIntersect 返回false 之后仍然调用它。
      【解决方案7】:

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

      假设你想使用一些里面的函数

      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;
      }
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多