C++ 构造函数中的错误控制

C++ 构造函数中的错误控制,c++,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_; } }; 类圆{ int x; int-y; 整数半径; 公众: 圆(整数x,整数y,整数半径){ x=x; y=y;

有这样一类:

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_;
  }
};
类圆{
int x;
int-y;
整数半径;
公众:
圆(整数x,整数y,整数半径){
x=x;
y=y;
如果(半径<0)
信号误差;
半径=半径;
}
};

不可能创建半径小于零的圆。在构造函数中发出错误信号的好方法是什么?我知道有例外,但是还有其他的方法吗?< /p> < p>考虑在构造函数后面调用一个方法(I/P>< P>不)设置C++中的RADIUS。抛出一个异常——这是从CTOR中跳出的标准方法。

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

抛出异常。这就是问题所在。

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

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

第三种选择是@Luchian提出的;我正在重新措辞

如果半径小于0,则不要调用构造函数。你应该有一个 在对象创建外部进行检查

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

if(radius < 0)  // should be 'radius_'
if(radius<0)//应该是“radius”

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

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

“适当”的解决方案是编写一个自定义的
非负的
(或者更一般地说,
约束的\u值
)类型,使编译器捕捉到这个错误——但对于某些项目来说,这可能会带来太多的开销,因为它很容易导致类型的激增。由于它提高了(编译时)类型安全性,我仍然认为这基本上是正确的方法

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

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

严格C++。例外情况是唯一一个“好”方法


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

这只是对已经存在的内容进行补充的另一种可能性:您可以模仿人们在ML语言中的行为,并制作一个“智能构造函数”:

这可能返回NULL


尽管我同意抛出异常可能是您想要的。

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

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

#包括
...
断言(半径>=0);
这将禁用对发布版本的检查

第三种选择是使用您自己的数据类型,作为post条件保证它永远不小于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_;
};
模板
类半径{
公众:
半径(标量常数和半径):半径(半径){
断言(半径>=标量(0));
if(ThrowRuntimeError){
如果(标量(0)>=radius)抛出std::runtime_错误(“不能为负”);
}
}
...
私人:
半径();/=删除;
标量半径;
};
请注意,在这种通用实现中,必须小心避免编译器警告,这些警告会警告对零进行不必要的比较(例如,当
Scalar=unsigned int
时)


无论如何,我不使用Radius类,而是使用Assertation(如果负Radius只能是一个bug)或exception(如果负Radius可能是错误输入的结果)。

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

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


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

是的,抛出异常是最好的,但问题是“还有其他方法吗?”。我认为Luchian指出了一个不错的问题。Owen,你应该总是对这些问题持怀疑态度,因为它们往往源于荒谬的“异常太慢”原因…@Owen:这种方法不会在构造函数中发出错误信号,它只是延迟问题,直到它不在构造函数中。让半径成为无符号整数,这样就不会发生这种情况如何?@Kerrek,这会使情况恶化。如果从有符号到无符号忽略了警告,则会创建一个巨大的地址圆。@iammilind:如果忽略警告,则是您自己的错误。半径为零的圆是否有效?简单地取负半径的绝对值是否合理?@iammilind:“大地址圈”是什么?不,这是个糟糕的主意,我投了反对票。构造的对象应该已经完成并准备好使用。@LuchianGrigore:您不能忘记调用ctor,或者在同一个对象上调用它两次。您可能会忘记调用
init
,或者其他任何东西。更不用说对象在构造和
init
之间处于未定义状态。对象构造应该完全在ctor中完成,这就是它们的全部意义。@Luchian:总是验证你
Circle* makeCircle(...) { ... }
#include <cassert>

...
    assert (radius >= 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_;
};