构造函数中的错误控制

时间:2011-07-28 11:56:45

标签: c++

有这样的课程:

class Circle{
  int x;
  int y;
  int radius;
public:
  Circle(int x_, int y_, int radius_){
    x = x_;
    y = y_;
    if(radius < 0)
      signal error;
    radius = radius_;
  }
};

无法创建半径小于零的圆。在构造函数中发出错误信号的好方法是什么?我知道有例外,但还有其他方法吗?

10 个答案:

答案 0 :(得分:9)

一个好的方法是不允许编译错误!

不幸的是,仅仅使用unsigned阻止将负值传递给构造函数 - 它将被隐式转换为unsigned值,从而隐藏错误(正如Alf在评论中指出的那样)。

“正确”的解决方案是将自定义nonnegative(或更一般地,constrained_value)类型写入 make 编译器捕获此错误 - 但对于某些项目这可能是太多的开销,因为它很容易导致类型的扩散。由于它提高了(编译时)类型安全性,我仍然认为这基本上是正确的方法。

这种约束类型的简单实现如下:

struct nonnegative {
    nonnegative() = default;
    template <typename T,
              typename = typename std::enable_if<std::is_unsigned<T>::value>::type>
    nonnegative(T value) : value{value} {}

    operator unsigned () const { return value; }

private:
    unsigned value;
};

See it in action

这可以防止除了无符号类型之外的任何构造。换句话说,它只是禁用从signedunsigned数字的通常隐式有损转换。

如果在编译时无法捕获错误(因为从用户那里收到了值),在大多数情况下,类似异常的东西是下一个最佳解决方案(尽管替代方法是使用option类型或类似的东西相似)。

通常你会在某种ASSERT宏中封装这种抛出异常。在任何情况下,用户输入验证不应该在类的构造函数内发生,而应该在从外部世界读取值之后立即发生。在C ++中,最常见的策略是依赖格式化输入:

unsigned value;
if (! (std::cin >> value))
    // handle user input error, e.g. throw an exception.

答案 1 :(得分:7)

没有。抛出异常 - 这是从ctor中拯救的标准方法。

答案 2 :(得分:4)

抛出异常。这就是RAII的全部内容。

答案 3 :(得分:2)

异常是从C ++中的构造函数报告错误的最佳方法。它比其他所有方法都安全和清晰。如果构造函数中发生错误,则不应创建该对象。抛出异常是实现这一目标的方法。

答案 4 :(得分:2)

异常是发出错误信号的正确方法。因为,将负值作为半径传递是一个逻辑错误。 这是safe to throw from constructor

作为其他选项,您可以将错误消息打印到日志文件中。

@Luchian有点建议第三种选择;我在改写它。

  

不要调用构造函数,如果半径大于&lt;你应该有一个   检查对象创建之外。

编辑:代码中存在逻辑错误:

if(radius < 0)  // should be 'radius_'

答案 5 :(得分:2)

我的个人意见是,您应该在一般情况下使用throw std::runtime_error,正如UncleBens在评论中指出的那样invalid_argument

在某些情况下,例如当通过不可预测的输入确保半径不能为负时,您应该使用assert代替,因为这是程序员错误(== bug):< / p>

#include <cassert>

...
    assert (radius >= 0);

这会禁用对发布版本的检查。

第三种选择是使用您自己的数据类型,该数据类型保证作为后置条件,它永远不会小于0;虽然必须注意不要发明太多的微型类型:

template <typename Scalar, bool ThrowRuntimeError>
class Radius {
public:
    Radius (Scalar const &radius) : radius_(radius) {
        assert (radius>=Scalar(0));
        if (ThrowRuntimeError) {
            if (Scalar(0) >= radius) throw std::runtime_error("can't be negative");
        }
    }

    ...

private:
    Radius(); // = delete;

    Scalar radius_;
};

请注意,在这样的一般实现中,必须小心避免编译器警告,警告不需要与零进行比较(例如,当Scalar = unsigned int时)。

无论如何,我没有使用Radius类,并使用断言(如果负半径只能是一个bug)或异常(如果负半径可能是输入错误的结果)。

答案 6 :(得分:1)

构造函数中的错误标准方法是使用异常。

然而,有时候有用的替代方案是“僵尸”方法。换句话说,如果你不能正确地构造一个工作对象,那么创建一个无功能的对象,但可以对此进行测试,并且可以安全地销毁它。所有方法也应该优雅地失败并成为NOP。

大多数时候这种方法只是一种烦恼,因为你会延迟发现问题,直到实际使用对象......但如果你有严重的理由,这是一条可行的途径避免例外。

答案 7 :(得分:0)

考虑在构造函数之后调用的init()方法中设置半径。

答案 8 :(得分:0)

严格来说是C ++ - 不......例外是唯一一个'好'的方式......

然而,还有许多其他“不太标准”的方法,我推荐两阶段构造函数(在symbian中使用)。

答案 9 :(得分:0)

添加到已经存在的内容中的另一种可能性是:您可以模仿人们在ML-ish语言中所做的事情,并创建一个“智能构造函数”:

Circle* makeCircle(...) { ... }

可能会返回NULL。

虽然我同意抛出异常可能是你想要的。