是允许函数抛出还是在构造函数中抛出更好?
Is it better allow a function to throw or throw in the constructor?
我认为,用一个例子来解释更容易。让我们用一个类来模拟一级方程式赛车的速度,界面可能看起来像这样:
class SpeedF1
{
public:
explicit SpeedF1(double speed);
double getSpeed() const;
void setSpeed(double newSpeed);
//other stuff as unit
private:
double speed_;
};
现在,负速度在这种特殊情况下没有意义,两个值都不大于500 km/h。在这种情况下,如果提供的值不在逻辑范围内,构造函数和setSpeed函数可能会抛出异常。
我可以引入一个额外的抽象层,插入一个额外的对象而不是double。新对象将是双精度对象的包装器,它是构造的,永远不会被修改。类的接口将更改为:
class ReasonableSpeed
{
public:
explicit ReasonableSpeed(double speed); //may throw a logic error
double getSpeed() const;
//no setter are provide
private:
double speed_;
};
class SpeedF1
{
public:
explicit SpeedF1(const ReasonableSpeed& speed);
ReasonableSpeed getSpeed() const;
void setSpeed(const ReasonableSpeed& newSpeed);
//other stuff as unit
private:
ReasonableSpeed speed_;
};
使用这种设计,SpeedF1不能抛出,但是每次我想重置速度时,我需要额外支付对象构造函数。
对于只有一组有限值是合理的类(例如日历中的Months),我通常将构造函数设为私有,并提供一组完整的静态函数。在这种情况下,这是不可能的,另一种可能性是实现一个空对象模式,但我不确定它是否优于简单地抛出一个异常。
最后,我的问题是:解决这类问题的最佳实践是什么?
首先,不要高估额外构造函数的成本。事实上,这个成本应该恰好是初始化double
的成本加上有效性检查的成本。换句话说,很可能等于使用原始double
。
其次,丢掉setter。 setter——以及较小程度上的getter——几乎总是反模式的。如果你需要设置一个新的(最高)速度,很可能你真的想要一辆新车。
现在,关于实际问题:一个抛出构造函数在原则上是完全没问题的。不要为了避免这种构造而编写复杂的代码。
另一方面,我也喜欢自检类型的想法。这充分利用了c++的类型系统,我非常赞成。
两种选择都有其优点。哪一个是最好的真的取决于具体情况。一般来说,我尽量利用类型系统和静态类型检查。在您的情况下,这意味着要为速度设置一个额外的类型。
我强烈支持第二种选择。这只是我个人的观点,没有太多的学术依据。我的经验是,设置一个只对有效数据进行操作的"纯"系统会使代码更加简洁。这可以通过使用第二种方法来实现,该方法确保只有有效的数据进入系统。
如果您的系统在增长,您可能会发现ReasonableSpeed
在很多地方被使用(使用您的判断力,但有可能事情实际上被重用了很多)。从长远来看,第二种方法将为您节省大量的错误检查代码。
如果只有一个类继承自reasonablesspeed,那么它似乎有点过头了。
如果很多类都继承或使用了ReasonableSpeed,那么它就是聪明的
当使用无效值作为速度时,您的两个设计都产生相同的结果,即它们都抛出异常。运用奥卡姆剃刀原理,或Unix稀有法则:
简约原则:只有当通过演示清楚了没有其他方法可以胜任的时候,才能编写一个大程序。
这里的"Big"既有代码量大的意思,也有内部复杂的意思。允许程序变大会损害可维护性。因为人们不愿意扔掉大量工作的可见成果,大型项目会导致对失败或次优方法的过度投资。
您可能会选择第一种更简单的方法。除非您想重用ReasonableSpeed
类。
我建议这样做:
class SpeedF1
{
public:
explicit SpeedF1(double maxSpeed);
double getSpeed() const;
void accelerate();
void decelerate();
protected:
void setSpeed(double speed);
//other stuff as unit
private:
double maxSpeed_;
double curSpeed_;
};
SpeedF1::SpeedF1(double maxSpeed) maxSpeed_(maxSpeed), curSpeed_(0.0) { }
double SpeedF1::getSpeed() const { return curSpeed_; }
void SpeedF1::setSpeed(double speed) {
if(speed < 0.0) speed = 0.0;
if(speed > maxSpeed_) speed = maxSpeed_;
curSpeed = speed;
}
void SpeedF1::accelerate() {
setSpeed(curSpeed_ + SOME_CONSTANT_VELOCITY);
}
void SpeedF1::decelerate() {
setSpeed(curSpeed_ - SOME_CONSTANT_VELOCITY);
}
- 初始化具有非默认构造函数的std::数组项的更好方法
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 对于等待以 std::future wait() 返回的函数的 CPU 使用率或检查标志在循环中休眠一段时间哪个更好?
- 在类的第一个/最后一个实例存在之前/之后调用一对函数.有没有更好的方法?
- 使用构造函数初始化结构还是在之后设置其值更好?
- 对于可移动类型,按值传递比重载函数更好吗?
- 为什么模板构造函数比复制构造函数更受欢迎?
- 在构造函数中组织初始值设定项列表的更好方法
- 哪种 c++ 构造函数样式更好
- Curly Braces构造函数更喜欢initializer_list而不是更好的匹配.为什么
- 使复制构造函数更灵活,可为ADT队列
- 调用包含所有所需代码的函数或内部包含所需代码的其他函数的函数更好?
- 在C++中使用类似 c 的初始化或构造函数初始化是否被认为更好
- 在 C++98 中实现移动构造函数和移动赋值运算符以获得更好的性能
- 是将基类强制转换为派生类更好,还是在基类上创建一个虚拟函数更好
- 当隐式move构造函数不够好时
- 是允许函数抛出还是在构造函数中抛出更好?
- c++ OOP哪一种方式给构造函数赋值更好
- 为什么写构造函数很好
- 两种类型的构造函数/哪一种更好