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

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


当前回答

我不能说明Objective-C中的最佳实践,但在c++中,构造函数抛出异常是可以的。特别是没有其他方法可以确保在不调用isOK()方法的情况下报告构造过程中遇到的异常情况。

函数try块特性是专门为支持构造函数成员初始化失败而设计的(尽管它也可以用于常规函数)。这是修改或丰富将要抛出的异常信息的唯一方法。但由于其最初的设计目的(在构造函数中使用),它不允许空catch()子句包含异常。

其他回答

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

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

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

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

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

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

齿顶高

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

Eric Lippert说有四种例外。

Fatal exceptions are not your fault, you cannot prevent them, and you cannot sensibly clean up from them. Boneheaded exceptions are your own darn fault, you could have prevented them and therefore they are bugs in your code. Vexing exceptions are the result of unfortunate design decisions. Vexing exceptions are thrown in a completely non-exceptional circumstance, and therefore must be caught and handled all the time. And finally, exogenous exceptions appear to be somewhat like vexing exceptions except that they are not the result of unfortunate design choices. Rather, they are the result of untidy external realities impinging upon your beautiful, crisp program logic.

构造函数本身不应该抛出致命异常,但它执行的代码可能会导致致命异常。像“内存不足”这样的事情不是您可以控制的,但是如果它发生在构造函数中,嘿,它就发生了。

愚蠢的异常永远不应该出现在任何代码中,所以它们应该被清除。

构造函数不应该抛出恼人的异常(例如Int32.Parse()),因为它们没有非异常情况。

最后,应该避免外生异常,但如果在构造函数中执行的某些操作依赖于外部环境(如网络或文件系统),则抛出异常是合适的。

参考链接:https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/

严格地从Java的角度来说,任何时候初始化使用非法值的构造函数时,它都应该抛出异常。这样它就不会被构造成一个糟糕的状态。

OO中通常的约定是对象方法确实有功能。

因此,作为一个推论,永远不要从构造函数/init中返回僵尸对象。

僵尸没有功能,可能缺少内部组件。只是一个空指针异常等待发生。

很多年前,我第一次在Objective C中制作僵尸。

就像所有的经验法则一样,也有一个“例外”。

一个特定的接口完全有可能有这样的契约 存在一个允许触发异常的方法“initialize”。 在调用initialize之前,实现此接口的对象可能不会正确响应除属性设置器之外的任何调用。在引导过程中,我在OO操作系统中的设备驱动程序中使用了这种方法,而且它是可行的。

一般来说,你不需要僵尸对象。在Smalltalk这样的语言中,使用变得有点冒火,但是过度使用变得也是一种糟糕的风格。be允许一个对象在原地变成另一个对象,因此不需要信封包装器(高级c++)或策略模式(GOF)。

请注意,如果您在初始化器中抛出异常,如果任何代码使用[[[MyObj alloc] init] autorelease]模式,那么您将最终泄漏,因为异常将跳过自动释放。

看这个问题:

在init中引发异常时如何防止泄漏?