构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
当前回答
构造函数的任务是使对象进入可用状态。关于这个问题,基本上有两种观点。
一组人赞成两阶段建设。构造函数只是将对象带入一个休眠状态,在这种状态下它拒绝做任何工作。还有一个额外的函数来进行实际的初始化。
我一直不明白这种方法背后的原因。我坚决支持单阶段构造,即对象在构造后完全初始化并可用。
如果单阶段构造函数未能完全初始化对象,则应该抛出。如果对象不能初始化,则必须不允许它存在,因此构造函数必须抛出。
其他回答
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。
OO中通常的约定是对象方法确实有功能。
因此,作为一个推论,永远不要从构造函数/init中返回僵尸对象。
僵尸没有功能,可能缺少内部组件。只是一个空指针异常等待发生。
很多年前,我第一次在Objective C中制作僵尸。
就像所有的经验法则一样,也有一个“例外”。
一个特定的接口完全有可能有这样的契约 存在一个允许触发异常的方法“initialize”。 在调用initialize之前,实现此接口的对象可能不会正确响应除属性设置器之外的任何调用。在引导过程中,我在OO操作系统中的设备驱动程序中使用了这种方法,而且它是可行的。
一般来说,你不需要僵尸对象。在Smalltalk这样的语言中,使用变得有点冒火,但是过度使用变得也是一种糟糕的风格。be允许一个对象在原地变成另一个对象,因此不需要信封包装器(高级c++)或策略模式(GOF)。
这总是很危险的,特别是当你在构造函数中分配资源时;根据你的语言,析构函数不会被调用,所以你需要手动清理。这取决于在您的语言中对象的生命周期何时开始。
我真正这么做的唯一一次是在某个地方出现了安全问题,这意味着对象不应该,而不是不能,被创建。
严格地从Java的角度来说,任何时候初始化使用非法值的构造函数时,它都应该抛出异常。这样它就不会被构造成一个糟糕的状态。
对我来说,这是一个有点哲学的设计决策。
拥有实例是非常好的,只要它们存在就有效,从ctor时间开始。对于许多重要的情况,如果无法进行内存/资源分配,则可能需要从ctor抛出异常。
其他一些方法是init()方法,它本身存在一些问题。其中之一是确保init()实际被调用。
一个变体使用惰性方法在第一次调用访问器/突变器时自动调用init(),但这要求任何潜在的调用者都必须担心对象是否有效。(而不是“它存在,因此它是有效的哲学”)。
我也看到过处理这个问题的各种设计模式。例如,可以通过ctor创建初始对象,但必须调用init()来获得包含的、初始化的具有访问器/突变器的对象。
每种方法都有其利弊;我已经成功地使用了所有这些方法。如果不是在创建对象的那一刻就创建现成的对象,那么我建议使用大量的断言或异常,以确保用户在init()之前不会进行交互。
齿顶高
我是从c++程序员的角度写的。我还假设您正确地使用了RAII习惯用法来处理抛出异常时释放的资源。