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

And*_*ndy 23 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);
};
Run Code Online (Sandbox Code Playgroud)

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

Time a = Time(12,34,56);
Time b = Time(12,34,65); // second is larger than 60
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 让构造函数抛出异常,并在调用函数中有处理程序来处理它.
  2. 在类中有一个标志,只有当构造函数接受这些值时才将其设置为true,并让程序在构造后立即检查标志.
  3. 有一个单独的(可能是静态的)函数来调用在调用构造函数之前立即检查输入参数.
  4. 重新设计类,以便可以从任何输入参数构造它.

哪种方法在工业中最常见?或者有什么我可能错过的?

sha*_*oth 38

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

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

  • @tim:当然不是我们使用例外的原因.不可否认,这个例子有点人为(这个问题还有其他的解决方法).但是在一般情况下,如果构造函数输入错误,则必须抛出异常.另一种选择是拥有一个无效的对象.无效对象将不遵守应用于其方法的任何约束.所以这里真正的解决方案是:来自用户的输入被验证(坏数据生成错误消息)然后在构造函数中使用(如果验证失败将抛出(这是好的)). (3认同)

Éri*_*ant 31

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

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

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

class Time{
public:
    Time(Hours h, Minutes m, Seconds s);
//...
};
Run Code Online (Sandbox Code Playgroud)

小时,分钟和秒是有界值.例如,使用(尚未)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;
Run Code Online (Sandbox Code Playgroud)

  • @tim:当你是30岁时,你打算如何处理小时(n)?在这种情况下,*Hours ctor*必须抛出异常!在系统内工作,在这种情况下意味着ctors必须抛出或成功,很难避免.(这当然有可能成为一个更好的整体解决方案 - 很难从一个人为的例子中得知 - 但重点是你不要避免例外.) (5认同)
  • 问题是,那些不应该发生的事情无论如何都会发生,这很烦人。Kernighan 和 Paugher 在“软件工具”中主张将“不可能发生”的 printf 语句放在代码可以检测到理论上不可能发生的情况的任何地方。他们报告说,他们多次看到“不可能发生”作为直接结果打印出来,感到沮丧和谦卑。 (2认同)

Ste*_*sop 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);
};
Run Code Online (Sandbox Code Playgroud)

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

  • 两阶段构造很糟糕,因为班级的用户必然会忘记它.它会干扰像"时间"这样的类应该实现的不可变值习语. (4认同)

Tim*_*mbo 11

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

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

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


Gal*_*man 5

"从C'tor抛出的异常"不是一个四个字母的单词.如果无法正确创建对象,则C'tor应该失败,因为您宁愿构造失败而不是无效对象.