是允许函数抛出还是在构造函数中抛出更好?

Is it better allow a function to throw or throw in the constructor?

本文关键字:构造函数 更好 函数 许函数      更新时间:2023-10-16

我认为,用一个例子来解释更容易。让我们用一个类来模拟一级方程式赛车的速度,界面可能看起来像这样:

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