如何在构造函数中处理不正确的值?

时间:2009-07-21 10:37:34

标签: c++ error-handling constructor return-value

请注意,这是关于构造函数的问题,而不是关于处理时间的类。

假设我有这样一个类:

class Time
{
protected:
    unsigned int m_hour;
    unsigned int m_minute;
    unsigned int m_second;
public:
    Time(unsigned int hour, unsigned int minute, unsigned int second);
};

虽然我希望成功构建一个,但我希望b的构造函数失败。

Time a = Time(12,34,56);
Time b = Time(12,34,65); // second is larger than 60

但是,这是不可能的,因为构造函数不会返回任何值,并且总是会成功。

构造函数如何告诉程序它不满意?我想到了几个方法:

  1. 让构造函数抛出异常,并在调用函数中有处理程序来处理它。
  2. 在类中有一个标志,只有当构造函数接受这些值时才将其设置为true,并让程序在构造后立即检查该标志。
  3. 有一个单独的(可能是静态的)函数来调用,在调用构造函数之前立即检查输入参数。
  4. 重新设计类,以便可以从任何输入参数构建它。
  5. 这些方法中哪一种在工业中最常见?或者有什么我可能错过的?

11 个答案:

答案 0 :(得分:38)

典型的解决方案是抛出异常。

背后的逻辑如下:构造函数是一种将一块内存转换为有效对象的方法。它成功(正常完成)并且你有一个有效的对象,或者你需要一些不可忽视的问题指标。例外是在C ++中使问题不可忽略的唯一方法。

答案 1 :(得分:31)

另一种选择,为了完整性:

  • 重新设计界面,使无效值“不可能”

例如,在“时间”课程中,您可以:

class Time{
public:
    Time(Hours h, Minutes m, Seconds s);
//...
};

小时,分钟和秒是有界值。例如,使用(尚未)Boost Constrained Value库:

typedef bounded_int<unsigned int, 0, 23>::type Hours;
typedef bounded_int<unsigned int, 0, 59>::type Minutes;
typedef bounded_int<unsigned int, 0, 59>::type Seconds;

答案 2 :(得分:12)

通常我会说(1)。但是如果你发现调用者都在使用try / catch包围构造,那么你也可以在(3)中提供静态辅助函数,因为通过一些准备工作,异常就不可能实现。

还有另一种选择,虽然它对编码风格有重大影响所以不应轻易采用,

5)不要将参数传递给构造函数:

class Time
{
protected:
    unsigned int m_hour;
    unsigned int m_minute;
    unsigned int m_second;
public:
    Time() : m_hour(0), m_minute(0), m_second(0) {}
    // either return success/failure, or return void but throw on error,
    // depending on why the exception in constructor was undesirable.
    bool Set(unsigned int hour, unsigned int minute, unsigned int second);
};

它被称为两阶段构造,并且在构造函数不希望或不可能抛出异常的情况下使用。使用nothrow new的代码,用-fno-exceptions编译,可能是经典案例。一旦你习惯了它,它就会比你最初想的那样烦人。

答案 3 :(得分:11)

还有一种可能的方法。我并不是说这是首选,只是为了完整性而添加它:

创建一个工厂函数,在堆上创建类的实例,如果创建失败则返回空指针。

对于类似于类型的对象而言,这不适合作为日期,但可能会有一些有用的应用程序。

答案 4 :(得分:5)

“从C'tor抛出的异常”不是一个四个字母的单词。 如果无法正确创建对象,则C'tor应该失败,因为您在构造上失败而不是使用无效对象。

答案 5 :(得分:3)

  
      
  • 让构造函数抛出一个异常,并在其中有处理程序   调用函数来处理它。
  •   

是。 Design by Contract并保留前置条件检查,如果失败,则抛出异常。没有无效的时间了。

  
      
  • 在类中有一个标志,只有在值的情况下才将其设置为true   构造函数可以接受,和   让程序检查标志   施工后立即。
  •   

也许。在复杂情况下可以接受,但如果检查失败则再次抛出。

  
      
  • 有一个单独的(可能是静态的)函数来调用来检查输入   呼叫前的参数   构造函数。
  •   

也许。这是关于告知输入数据是否正确,如果告诉它是非常重要的,可能会有用,但如果数据无效,请参阅上面的如何做出反应

  
      
  • 重新设计类,以便可以从任何输入参数构建它。
  •   

没有。你基本上会推迟问题。

答案 6 :(得分:3)

通常,您有一个私有/受保护的构造函数和一个公共静态工厂方法来创建Time对象。从构造函数中抛出异常并不是一个好主意,因为它会对继承造成严重破坏。这样,您的工厂方法可以在需要时抛出异常。

答案 7 :(得分:1)

第一个是最好的,例外是告知类用户有关错误的最佳方式。

不建议采用其他方式,因为如果构造函数返回没有错误,则表示您已正确构造了一个对象,并且可以在任何地方使用它

答案 8 :(得分:1)

只是详细说明onebyoneTimbo给出的答案。当人们讨论例外的使用时,通常会有人最终说:“在特殊情况下应该使用例外。”

从这里的大多数答案可以看出,如果构造函数失败,那么正确的响应是抛出异常。但是,在您的情况下,您不一定不能创建对象,而是想要创建对象。

在从外部源(例如文件或流)读取值的情况下,很可能会收到无效值,在这种情况下,它实际上不是例外情况。

就个人而言,我倾向于在构造时间对象之前验证参数(类似Timbo's回答),然后我会在构造函数中有一个断言来验证它们的参数是否有效。

如果您的构造函数需要一个资源(例如,分配内存),那么,恕我直言,将更接近异常情况,因此您将抛出异常。

答案 9 :(得分:1)

我认为你没有太多选择。

如果输入的输入无效,则无法向呼叫者发出信号,而无法发出信号。然后,您可以使用异常或错误代码。

构造函数中的错误代码需要作为引用参数传递,因此看起来非常不方便。

您可以尝试找到如何验证来源的输入。为什么你的输入完全无效?如何输入无效等。

日期类的一个示例是强制用户(程序用户)仅输入有效日期(例如,强制他在日历类型GUI中输入它)。

您还可以尝试在类中创建一个方法来处理输入验证。

有了这个,用户(这次的程序员)可以在构建之前调用它,并确保如果用户没有验证它,则调用不会失败或者返回异常。

如果性能很重要且你不想两次调用validate函数(用户调用它,那么在构造函数中),我认为你可以使用命名的构造函数idiom来拥有一个CheckedConstructor和一个UncheckedConstructor。

我认为这开始过于建筑过度了。

最后,它将取决于类和用例。

答案 10 :(得分:-1)

考虑一个类似工厂的模式来生成时间对象:

static bool Time::CreateTime(int hour, int min, int second, Time *time) {
  if (hour <= 12 && hour >= 0 && min < 60 && min >= 0 && 
      second < 60 && second >= 0)  {
     Time t(hour, min, second);
     *time = t;
     return true;
  }
  printf("Your sense of time seems to be off");
  return false;
}

Time t;
if (Time::CreateTime(6, 30, 34, &t)) {
  t.time(); // :)
} else {
  printf("Oh noes!");
  return;
}

这假设Time有:

  • 默认构造函数
  • 复制构造函数
  • 副本分配运算符