代码之家  ›  专栏  ›  技术社区  ›  Jack BeNimble

从C++中的错误条件中构造构造函数的最佳方法是什么?

  •  19
  • Jack BeNimble  · 技术社区  · 16 年前

    从C++中的错误条件中构造构造函数的最佳技术是什么?尤其是,打开文件时出错。

    感谢您的回复。我提出一个例外。这是密码(不知道是不是 最好的 方法很简单)

    // Test to see if file is now open; die otherwise 
    if ( !file.is_open() ) {
        cerr << "Failed to open file: " << m_filename << endl;
        throw ("Failed to open file");
    }   
    

    有人认为我喜欢C++是不必在方法声明上声明抛出异常的。

    8 回复  |  直到 16 年前
        1
  •  26
  •   Brian R. Bondy    16 年前

    最好的建议可能是Parashift所说的。但请阅读下面我的注意事项。

    See parashift FAQ 17.2

    [17.2]如何处理构造函数 失败了吗?

    引发异常。

    构造函数没有返回类型, 所以不能用退货 代码。最好的信号方式 因此,构造函数失败是为了 引发异常。如果你没有 使用异常的选项, “最不坏”的工作就是 对象进入“僵尸”状态 设置内部状态位,以便 物体的动作有点像死了一样 即使技术上它仍然 活着。

    “僵尸”对象的概念 很多都在下面。您需要添加 查询(“inspector”)成员函数到 检查这个“僵尸”位,这样用户 你的班级可以发现 物体是真实存在的,或者如果它是 僵尸(即“活死人”物体) 几乎每个地方你 构建一个对象 (包括在较大的物体内或 一组对象)您需要检查 通过if语句的状态标志。 您还需要将if添加到 其他成员函数:如果对象 是一个僵尸,不行动,或者也许 更令人讨厌的事情。

    实际上,“僵尸”的东西 相当丑陋。当然你应该 比僵尸对象更喜欢例外, 但是如果你没有选择 使用异常,僵尸对象可能 做“最不坏”的选择。


    在构造函数中引发异常时的注意事项:

    但是要非常小心,因为如果在构造函数中抛出异常,则不会调用类的析构函数。因此,在抛出异常之前,您需要小心地销毁已经构造的对象。一般来说,同样的警告也适用于异常处理,但在处理构造函数时,它可能不太明显。

    class B
    {
    public:
        B()
        {
    
        }
    
        virtual ~B()
        {
            //called after D's constructor's exception is called
        }
    };
    
    class D : public B
    {
    public:
        D()
        {
            p = new char[1024];
            throw std::exception("test");
        }
    
        ~D()
        {
          delete[] p;
          //never called, so p causes a memory leak
        }
    
        char *p;
    };
    
    int main(int argc, char **argv)
    {
    
        B *p;
        try
        {
            p = new D();
        }
        catch(...)
        {
    
        }
    
    
        return 0;
    }
    

    使用CreateInstance方法的受保护/私有构造函数:

    另一种解决方法是将构造函数设置为私有或受保护,并创建一个可返回错误的CreateInstance方法。

        2
  •  7
  •   Adam Rosenfield    16 年前

    您可以像其他人提到的那样抛出一个异常,或者您也可以重构您的代码,这样您的构造函数就不会失败。例如,如果您正在处理一个禁用或不允许异常的项目,那么后者是您的最佳选择。

    若要生成不能失败的构造函数,请将可能失败的代码重构为 init() 方法,并让构造函数尽可能少地执行工作,然后要求类的所有用户调用 () 施工后立即进行。如果 () 失败,您可以返回错误代码。一定要在你班的文档中记录这一点!

    当然,这有点危险,因为程序员可能忘记调用 () . 编译器无法强制执行此操作,因此请仔细阅读,并尝试在以下情况下使代码快速失败: () 不被调用。

        3
  •  4
  •   anon    16 年前

    一般来说,您应该抛出一个异常。另一种选择是拥有一些半正确构造的对象,用户必须以某种方式对其进行测试,而这将不可避免地失败。

        4
  •  2
  •   Dan Breslau    16 年前

    如果您正在构造的对象由于错误而无效,并且需要由调用方处理,那么您几乎必须抛出异常。这允许编译器执行正确的资源释放。

    (编写异常安全的构造函数需要一点注意——简而言之,您需要尽可能地使用初始值设定项列表,而不是使用构造函数主体——但是,如果您遇到这样的情况,抛出异常的可能性很大,这一点很关键。)

        5
  •  1
  •   Mykola Golubyev    16 年前

    如果你在错误之后拒绝了它,你就不能执行它的操作-你必须抛出。如果它可以记录您的错误并更改构造逻辑。

        6
  •  0
  •   Naveen    16 年前

    引发异常。请在此处查看更多信息: Handling error in constructor

        7
  •  0
  •   Greg Domjan    16 年前

    只有一个好方法可以从出错的构造函数中退出,即引发异常。

    这真的是个错误吗?您是否试图向构造函数添加太多内容?

    通常,人们会尝试将一些初始交互转入构造函数,比如将文件名添加到文件构造函数中。是否希望它立即打开该文件,或者您只是设置了某种状态,它与file.open(文件名)是否不同,如果失败是否正常?

        8
  •  0
  •   David Thornley    16 年前

    最好是抛出一个异常。这就是他们的目的,任何试图复制你得到的行为的尝试都可能在某个地方失败。

    如果由于某种原因无法使用异常,请使用 nothrow . 标准18.4.1.1第9条中的示例为:

    t* p2 = new(nothrow) T;    // returns 0 if it fails
    

    从技术上讲,这是一种新的放置形式,但是它应该返回一个完全成形的对象或者一个需要测试的空指针,除非没有人会这样做。

    如果您的类可以有一个存在但未正确初始化的对象,那么您可以有一个数据成员作为标记来判断该类是否有用。同样,没有人会在实时代码中检查该标志。

    请记住,如果您确实需要一个保证不会失败的分配,那么您需要提前分配内存并使用placement new,并删除可能会抛出到另一个例程的所有初始化,而其他人将无法调用该例程。分配内存的任何东西都可能失败,特别是在那些通常不支持异常的更为有限的系统上。

    事实上,例外是最好的方式。