有这样的课程:
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_;
}
};
无法创建半径小于零的圆。在构造函数中发出错误信号的好方法是什么?我知道有例外,但还有其他方法吗?
答案 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;
};
这可以防止除了无符号类型之外的任何构造。换句话说,它只是禁用从signed
到unsigned
数字的通常隐式有损转换。
如果在编译时无法捕获错误(因为从用户那里收到了值),在大多数情况下,类似异常的东西是下一个最佳解决方案(尽管替代方法是使用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。
虽然我同意抛出异常可能是你想要的。