C++ 如何处理构造函数中的错误值?

C++ 如何处理构造函数中的错误值?,c++,error-handling,constructor,return-value,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); }; 虽然我希望a成功构造,但我希望b的构造函

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

假设我有一个这样的类:

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);
};
虽然我希望a成功构造,但我希望b的构造函数失败

Time a = Time(12,34,56);
Time b = Time(12,34,65); // second is larger than 60
但是,这是不可能的,因为构造函数不返回任何值,并且总是成功的

构造函数如何告诉程序它不高兴?我想到了几种方法:

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

  • 以下哪种方法在工业中最常见?或者我有什么遗漏吗?

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


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

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

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


    这对于像valuetype这样的对象作为日期并不合适,但可能有一些有用的应用程序。

    第一个是最好的,异常是通知类用户错误的最好方法

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

    “从命令引发的异常”不是四个字母的单词。 如果不能正确创建一个对象,那么C'tor应该失败,因为你宁愿在构造中失败,也不愿拥有一个无效的对象。

    通常我会说(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异常一起编译。一旦你习惯了它,它会比你一开始想的稍微少一点烦人。

    请详细说明一下和给出的答案。当人们讨论异常的使用时,通常有人最终会说:“异常应该在异常情况下使用。”

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

    当从外部源(例如文件或流)读取值时,很有可能会接收到无效值,在这种情况下,这并不是一种真正的例外情况

    就个人而言,我倾向于在构建时间对象(类似于答案)之前验证参数,然后在构造函数中使用断言来验证这些参数是否有效

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

    • 让构造函数抛出异常,并在 调用函数来处理它
    对。并保持先决条件检查处于打开状态,如果失败,则抛出异常。没有无效的时间了

    • 在类中有一个标志,仅当值为 建造商可接受,以及 让程序检查标志 施工后立即进行
    也许吧。在复杂情况下可以接受,但如果检查失败,请再次抛出

    • 有一个单独的(可能是静态的)函数来调用以检查输入 调用前的参数 构造器
    也许吧。这是关于判断输入数据是否正确的,如果判断数据是否正确可能会很有用,但请参见上文,了解在数据无效的情况下如何作出反应

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

    不。您基本上可以推迟这个问题。

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

    另一种选择是完整性:

    • 重新设计接口,使无效值“不可能”
    例如,在“时间”课程中,您可以:

    class Time{
    public:
        Time(Hours h, Minutes m, Seconds s);
    //...
    };
    
    小时、分钟和秒是有界值。例如,对于(尚未)Boost约束值库:

    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;
    
    typedef\u int::键入小时数;
    typedef_int::键入分钟数;
    typedef bounded_int::键入秒;
    
    我认为你没有太多选择

    如果你
    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;
    }