构造函数中的错误控制
Error control in constructors
有这样的类:
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类型以外的任何类型。换句话说,它只是禁用了通常从signed
到unsigned
的隐式有损转换。
如果在编译时不能捕获错误(因为值是从用户接收的),在大多数情况下,类似异常的东西是次优解决方案(尽管另一种选择是使用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()方法中设置半径
- 模板,函数使用错误的构造函数来复制我的对象
- std::allocator_traits::construct调用了错误的构造函数
- 错误:没有构造函数的实例与参数列表匹配
- 调用了错误的构造函数
- 错误 LNK2005:构造函数已定义
- C 汇编错误:对构造函数的引用是模棱两可的
- 导致错误的构造函数中的C++类型特征
- C++.对象使用错误的构造函数实例化
- 复制构造函数错误:从构造函数返回值
- 错误:没有构造函数修复的实例::修复与参数列表 (C++) 匹配
- 错误LNK2005构造函数已定义
- 错误:候选构造函数(隐式移动构造函数)不可行:没有已知的转换
- 结构错误的构造函数(调用类的构造函数 denconstructor,用于填充结构)
- C++ 异常的双重释放损坏错误(复制构造函数不起作用)
- c++错误没有构造函数的实例
- 文件I/O错误复制构造函数
- 错误的构造函数初始化
- 错误:命名构造函数,而不是类型.使用g++4.6.1编译时
- 模板:如何使用模板变量控制构造函数参数的数量
- 在自定义向量类中调用了错误的构造函数