构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
当前回答
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。
其他回答
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/
我所见过的关于异常的最好建议是,当且仅当替代方案是未能满足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语言和库中通过抛出异常来表示。
是的,如果构造函数未能构建其内部部分之一,则它可以(通过选择)有责任抛出(并以某种语言声明)一个显式异常,这在构造函数文档中有适当的说明。
This is not the only option: It could finish the constructor and build an object, but with a method 'isCoherent()' returning false, in order to be able to signal an incoherent state (that may be preferable in certain case, in order to avoid a brutal interruption of the execution workflow due to an exception) Warning: as said by EricSchaefer in his comment, that can bring some complexity to the unit testing (a throw can increase the cyclomatic complexity of the function due to the condition that triggers it)
如果它因为调用者而失败(比如调用者提供了一个空参数,而被调用的构造函数需要一个非空参数),构造函数无论如何都会抛出一个未检查的运行时异常。
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。
如果无法在构造函数中初始化对象,则抛出异常,一个例子是非法参数。
根据一般经验,应该尽可能快地抛出异常,因为当问题的根源接近发出错误信号的方法时,这会使调试变得更容易。