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