构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
当前回答
There is generally nothing to be gained by divorcing object initialization from construction. RAII is correct, a successful call to the constructor should either result in a fully initialized live object or it should fail, and ALL failures at any point in any code path should always throw an exception. You gain nothing by use of a separate init() method except additional complexity at some level. The ctor contract should be either it returns a functional valid object or it cleans up after itself and throws.
考虑一下,如果实现了单独的init方法,仍然必须调用它。它仍然有可能抛出异常,它们仍然必须被处理,而且它们实际上总是必须在构造函数之后立即被调用,除了现在你有4种可能的对象状态而不是2种(IE,已构造,初始化,未初始化,失败vs只是有效和不存在)。
In any case I've run across in 25 years of OO development cases where it seems like a separate init method would 'solve some problem' are design flaws. If you don't need an object NOW then you shouldn't be constructing it now, and if you do need it now then you need it initialized. KISS should always be the principle followed, along with the simple concept that the behavior, state, and API of any interface should reflect WHAT the object does, not HOW it does it, client code should not even be aware that the object has any kind of internal state that requires initialization, thus the init after pattern violates this principle.
其他回答
我不确定是否有答案可以完全与语言无关。有些语言处理异常和内存管理的方式不同。
I've worked before under coding standards requiring exceptions never be used and only error codes on initializers, because developers had been burned by the language poorly handling exceptions. Languages without garbage collection will handle heap and stack very differently, which may matter for non RAII objects. It is important though that a team decide to be consistent so they know by default if they need to call initializers after constructors. All methods (including constructors) should also be well documented as to what exceptions they can throw, so callers know how to handle them.
我通常支持单阶段构造,因为很容易忘记初始化对象,但也有很多例外。
Your language support for exceptions isn't very good. You have a pressing design reason to still use new and delete Your initialization is processor intensive and should run async to the thread that created the object. You are creating a DLL that may be throwing exceptions outside it's interface to an application using a different language. In this case it may not be so much an issue of not throwing exceptions, but making sure they are caught before the public interface. (You can catch C++ exceptions in C#, but there are hoops to jump through.) Static constructors (C#)
当构造函数无法完成所述对象的构造时,它应该抛出异常。
例如,如果构造函数应该分配1024 KB的ram,但它没有这样做,它应该抛出一个异常,这样构造函数的调用者就知道对象还没有准备好使用,并且在某个地方存在需要修复的错误。
半初始化和半死亡的对象只会引起问题和问题,因为调用者确实没有办法知道。我宁愿让我的构造函数在出错时抛出一个错误,而不是不得不依赖编程来运行返回true或false的isOK()函数的调用。
由于部分创建的类可能导致的所有麻烦,我认为永远不会。
如果需要在构造过程中验证某些内容,请将构造函数设为私有并定义一个公共静态工厂方法。如果某些东西无效,该方法可以抛出。但是如果一切都检查出来了,它就调用构造函数,保证不会抛出。
OO中通常的约定是对象方法确实有功能。
因此,作为一个推论,永远不要从构造函数/init中返回僵尸对象。
僵尸没有功能,可能缺少内部组件。只是一个空指针异常等待发生。
很多年前,我第一次在Objective C中制作僵尸。
就像所有的经验法则一样,也有一个“例外”。
一个特定的接口完全有可能有这样的契约 存在一个允许触发异常的方法“initialize”。 在调用initialize之前,实现此接口的对象可能不会正确响应除属性设置器之外的任何调用。在引导过程中,我在OO操作系统中的设备驱动程序中使用了这种方法,而且它是可行的。
一般来说,你不需要僵尸对象。在Smalltalk这样的语言中,使用变得有点冒火,但是过度使用变得也是一种糟糕的风格。be允许一个对象在原地变成另一个对象,因此不需要信封包装器(高级c++)或策略模式(GOF)。
我所见过的关于异常的最好建议是,当且仅当替代方案是未能满足post条件或保持不变时,抛出异常。
该建议将不明确的主观决策(这是个好主意吗)替换为基于设计决策(不变条件和后置条件)的技术精确问题。
Constructors are just a particular, but not special, case for that advice. So the question becomes, what invariants should a class have? Advocates of a separate initialization method, to be called after construction, are suggesting that the class has two or more operating mode, with an unready mode after construction and at least one ready mode, entered after initialization. That is an additional complication, but acceptable if the class has multiple operating modes anyway. It is hard to see how that complication is worthwhile if the class would otherwise not have operating modes.
请注意,将setup推入单独的初始化方法中并不能避免抛出异常。构造函数可能抛出的异常现在将由初始化方法抛出。如果为未初始化的对象调用类中所有有用的方法,都必须抛出异常。
还要注意,避免构造函数抛出异常的可能性是很麻烦的,在许多标准库中在许多情况下是不可能的。这是因为这些库的设计者认为从构造函数抛出异常是个好主意。特别是,任何试图获取不可共享或有限资源(例如分配内存)的操作都可能失败,而这种失败通常在OO语言和库中通过抛出异常来表示。