在这种情况下,重载bool操作符是危险的

时间:2013-12-05 08:42:27

标签: c++ casting boolean operator-overloading

我已经看到SoF上的评论或答案,说明将强制转换操作符重载到bool是危险的,我应该更喜欢void* operator。我仍然想问一下,在我的用例中使用此运算符是否危险,如果是,为什么。

我实现了一个简单的几何库,其中一个最基本的类是我用以下方式定义的一个点:

struct point {
  double x, y;
  bool real;
  point(double x_, double y_): x(x_), y(y_), real(true) {}
  point(): x(0), y(0), real(true) {}

  operator bool() {
    return real;
  }
};

我将尝试解释为什么我需要使用强制转换操作符来布尔。我有另一个名为line的类,我有一个声明如下的函数:

point intersect(const line& a, const line& b);

现在已经将强制转换操作符重载到一个点,我可以写两个:point p = intersect(a,b);if (intersect(a,b))因此得到两条线的交点或检查两条线是否相交。我也在其他地方使用它,但我相信这个例子足以显示强制转换运算符的用法。我对演员操作员的使用是否危险,如果可以,请在效果不符合预期时举例说明?

编辑:由于juanchopanza的一个小小的补充:在这个项目中,我仅限于不使用c ++ 11,因此我无法使运算符显式化。

6 个答案:

答案 0 :(得分:5)

这还不够好。由于你坚持使用C ++ 03,如果你需要这样的东西,你应该使用safe-bool idiom(否则,explicit转换函数应该是首选)。

另一种解决方案是返回boost:optional

boost::optional<point> intersect(const line& a, const line& b);

我会采用这种方法(即使在C ++ 11中),因为它在语义上看起来更好 - 并行线没有交叉点,所以返回值确实应该是可选(或< em>也许值。)

答案 1 :(得分:3)

是的,这很危险。例如,让我们完全按原样考虑您的point,没有operator+的定义。尽管如此,如果我们尝试添加两个point对象,编译器根本不会反对:

point a, b;

std::cout << a + b;

这可以通过将每个point转换为bool,然后添加bool来实现。在整数上下文中,false转换为0,true转换为1,因此我们可以期望上面的代码打印出来2.当然,我只是使用了附加作为示例。您也可以进行减法,乘法,除法,按位运算,逻辑运算等。所有这些都将编译和执行,但显然会产生毫无价值的结果。同样,如果我们将point传递给只接受数字类型的某个函数,编译器将不会停止或抱怨 - 它只会转换为bool,并且该函数将获得01取决于real是否为真。

安全布尔成语是安全的(好吧,不管怎么说更危险),因为你可以在布尔上下文中测试void *,但是void *不会隐式地转换为其他任何类似的东西。 bool会。你(主要是 1 )不会意外地对它们进行算术运算,按位运算等。


<子> 1.无论如何都有几个漏洞,主要涉及调用其他在void *上进行某种显式转换的东西。能够标记转换运算符explicit更好,但老实说,它们在可读性方面比安全性提高了很多。

答案 2 :(得分:2)

奇怪的是那些人没有告诉你为什么重载operator bool()是危险的。

原因是C ++具有从bool到所有其他数字类型的隐式转换。因此,如果您重载operator bool(),那么您将丢失该类用户通常期望的大量类型检查。他们可以在pointint预期的任何地方提供float

重载operator void*()的危险性较小,因为void*转换为较少,但仍然存在void*类型用于其他事情的相同基本问题。

安全布尔习语的想法是返回一个指针类型,该指针类型不会转换为任何API中使用的任何

请注意,如果您愿意写if (intersect(a,b).real)if (intersect(a,b).exists()),则无需担心这一点。 C ++ 03 有明确的转换。任何带有一个参数的函数(或没有参数的非静态成员函数)都是对各种类型的转换。 C ++ 03只是没有显式转换运算符

答案 3 :(得分:1)

由于从bool到数值的隐式转换,您可能会遇到各种麻烦。 此外,你的线类有一个(在我的观点)dangerious成员'bool real',以携带函数调用的结果。如果线不相交,则返回交叉点值或NaN的交叉函数可以解决您的问题。

实施例

有一个(无限)线类,包含一个原点p和一个向量v:

struct Line {
    Point p;
    Vector v;

    Point operator () (double r) const {
        return p + r * v;
    }
};

计算交点值的函数

/// A factor suitable to be passed to line a as argument to calculate the
/// inersection point.
/// - A value in the range [0, 1] indicates a point between
///   a.p and a.p + a.v.
/// - The result is NaN if the lines do not intersect. 
double intersection(const Line& a, const Line& b) {
    double d = a.v.x * b.v.y - a.v.y * b.v.x;
    if( ! d) return std::numeric_limits<double>::quiet_NaN();
    else {
        double n = (b.p.x - a.p.x) * b.v.y
                 - (b.p.y - a.p.y) * b.v.x;
        return n/d;
    }
}

然后你可以这样做:

double d = intersection(a, b);
if(d == d) { // std::isnan is c++11
    Point p = a(d);
}

或者

if(0 <= d && d <= 1) {
    Point p = a(d);
}

答案 4 :(得分:0)

例如,早期在C ++ 2003 Standard类中,std :: basic_ios具有以下转换运算符

operator void*() const

现在,由于新的C ++标准具有关键字显式,因此该运算符替代了

explicit operator bool() const;

因此,如果您要为运营商添加关键字显式,我认为这不会有危险。:)

另一种方法是定义operator bool operator!()而不是转换运算符。

答案 5 :(得分:0)

虽然已经有一些非常好的答案,但我想提出一些更完整的答案,并解释为什么提出的解决方案能够发挥作用。

1.为什么我的解决方案有误? 编译器执行从bool到数字类型的隐式转换,从而导致定义了意外的运算符。例如:

point p;
if (p < 1)

在我没想到的时候会编译。将转换为operator void*()的运算符更改将会改善一些事情,但只会略微因为为void *定义的某些运算符(尽管少于bool)而且它们的存在将再次导致意外行为。

2.那么什么是正确的解决方案。可以找到正确的解决方案here。但是我会为我的课程添加代码,以便我可以解释它的工作原理以及为什么我们以这种方式解决问题:

class point {
  typedef void (point::*bool_type)() const;
  void not_supported() const; // NOTE: only declaration NO body!

public:
  double x, y;
  bool real;
  point(double x_, double y_): x(x_), y(y_), real(true) {}
  point(): x(0), y(0), real(true) {}

  operator bool_type() {
    return (real == true)? &point::not_supported : 0;
  }

  template<typename T>
  bool operator==(const T& rhs) const {
    not_supported();
    return false;
  }

  template<typename T>
  bool operator!=(const T& rhs) const {
    not_supported();
    return false;
  }
};

现在我首先解释为什么我们必须使用一个函数指针而不是普通指针的类型。答案由Steve Jessop在Nawaz的答案下面的评论中说明。 function pointers are not < comparable因此,任何写p < 5的尝试或点与其他类型之间的任何其他比较都将无法编译。

为什么我们需要一个没有身体的方法(not_supported)?因为这样每次我们尝试实例化用于比较的模板化方法(即==!=)时,我们将对没有主体的方法进行函数调用,这将导致编译错误。