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

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


当前回答

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项,编写异常安全的代码。

其他回答

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

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

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

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

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

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

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

如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。

在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!

本页对c++中的情况进行了全面的讨论。

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.

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项,编写异常安全的代码。

当构造函数无法完成所述对象的构造时,它应该抛出异常。

例如,如果构造函数应该分配1024 KB的ram,但它没有这样做,它应该抛出一个异常,这样构造函数的调用者就知道对象还没有准备好使用,并且在某个地方存在需要修复的错误。

半初始化和半死亡的对象只会引起问题和问题,因为调用者确实没有办法知道。我宁愿让我的构造函数在出错时抛出一个错误,而不是不得不依赖编程来运行返回true或false的isOK()函数的调用。