构造函数中的错误控制

Error control in constructors

本文关键字:错误控制 构造函数      更新时间:2023-10-16

有这样的类:

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

不可能创建半径小于零的圆。什么是一个好的方法来信号错误的构造函数?我知道有例外,但是还有其他方法吗?

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

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

"适当"的解决方案是编写一个自定义的nonnegative(或者更一般地说,constrained_value)类型来使编译器捕获此错误-但是对于某些项目来说,这可能会带来太多的开销,因为它很容易导致类型的扩散。因为它提高了(编译时)类型安全,所以我仍然认为这基本上是正确的方法。

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

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

实际操作

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

如果在编译时不能捕获错误(因为值是从用户接收的),在大多数情况下,类似异常的东西是次优解决方案(尽管另一种选择是使用option类型或类似的东西)。

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

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

No抛出异常——这是从actor中退出的标准方法。

抛出异常。这就是RAII的意义所在。

Exception是表示错误的正确方式。因为,传递一个负值作为半径是一个逻辑错误。从构造函数中使用throw是安全的。

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

第三种选择是@Luchian建议的;我在重新表述。

不要调用构造函数,如果半径是<0. 你应该有一个在对象创建之外检查。

编辑:你的代码中有一个逻辑错误:

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

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

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

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

#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类,并使用断言(如果负半径只能是错误)或异常(如果负半径可能是错误输入的结果)。

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

然而,有时有用的另一种方法是"僵尸"方法。换句话说,如果你不能正确地构造一个工作对象,那么就创建一个非功能的对象,但它可以被测试,并且可以安全地销毁。所有的方法也应该优雅地失败并成为nop。

大多数情况下,这种方法只是一个烦恼,因为您将延迟发现问题,直到对象实际使用时…但如果你有充分的理由避免异常,这是一条可行的路径。

严格c++ -不…例外是唯一"好"的方法…

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

这是另一种可能性:您可以模仿人们在ML-ish语言中所做的事情,并创建一个"智能构造函数":

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

可能返回NULL。

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

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