构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)

在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?


当前回答

如果你正在编写ui控件(ASPX, WinForms, WPF,…),你应该避免在构造函数中抛出异常,因为设计器(Visual Studio)在创建控件时无法处理它们。了解你的控件生命周期(控件事件),尽可能使用惰性初始化。

其他回答

对我来说,这是一个有点哲学的设计决策。

拥有实例是非常好的,只要它们存在就有效,从ctor时间开始。对于许多重要的情况,如果无法进行内存/资源分配,则可能需要从ctor抛出异常。

其他一些方法是init()方法,它本身存在一些问题。其中之一是确保init()实际被调用。

一个变体使用惰性方法在第一次调用访问器/突变器时自动调用init(),但这要求任何潜在的调用者都必须担心对象是否有效。(而不是“它存在,因此它是有效的哲学”)。

我也看到过处理这个问题的各种设计模式。例如,可以通过ctor创建初始对象,但必须调用init()来获得包含的、初始化的具有访问器/突变器的对象。

每种方法都有其利弊;我已经成功地使用了所有这些方法。如果不是在创建对象的那一刻就创建现成的对象,那么我建议使用大量的断言或异常,以确保用户在init()之前不会进行交互。

齿顶高

我是从c++程序员的角度写的。我还假设您正确地使用了RAII习惯用法来处理抛出异常时释放的资源。

由于部分创建的类可能导致的所有麻烦,我认为永远不会。

如果需要在构造过程中验证某些内容,请将构造函数设为私有并定义一个公共静态工厂方法。如果某些东西无效,该方法可以抛出。但是如果一切都检查出来了,它就调用构造函数,保证不会抛出。

Throwing an exception during construction is a great way to make your code way more complex. Things that would seem simple suddenly become hard. For example, let's say you have a stack. How do you pop the stack and return the top value? Well, if the objects in the stack can throw in their constructors (constructing the temporary to return to the caller), you can't guarantee that you won't lose data (decrement stack pointer, construct return value using copy constructor of value in stack, which throws, and now have a stack that just lost an item)! This is why std::stack::pop does not return a value, and you have to call std::stack::top.

这个问题在这里有很好的描述,检查第10项,编写异常安全的代码。

参见c++常见问题解答第17.2和17.4节。

一般来说,我发现如果构造函数被编写,那么它们就不会失败,那么移植和维护结果的代码就会更容易,而可能失败的代码则放在一个单独的方法中,该方法返回错误代码并使对象处于惰性状态。

这总是很危险的,特别是当你在构造函数中分配资源时;根据你的语言,析构函数不会被调用,所以你需要手动清理。这取决于在您的语言中对象的生命周期何时开始。

我真正这么做的唯一一次是在某个地方出现了安全问题,这意味着对象不应该,而不是不能,被创建。