构造函数何时应该引发异常?
构造函数何时应该引发异常? (或者在目标C的情况下:初始化者何时返回nil是正确的?)
在我看来,如果对象不完整,则构造函数应该失败-因此拒绝创建对象。即,构造函数应与其调用者签订合同,以提供可以有效调用方法的功能和工作对象?那合理吗?
解决方案
回答
ctor不应该做任何"聪明"的事情,因此无论如何都不需要抛出异常。如果要执行一些更复杂的对象设置,请使用Init()或者Setup()方法。
回答
由于部分创建的类可能引起的所有麻烦,我永远不会说。
如果我们需要在构造过程中验证某些内容,请将构造函数设为私有并定义公共静态工厂方法。如果某些无效,则该方法可能会抛出该异常。但是,如果一切都完成,它将调用构造函数,以确保不会抛出异常。
回答
这总是很狡猾,尤其是当我们在构造函数内部分配资源时。根据语言,不会调用析构函数,因此我们需要手动清理。它取决于对象的生存时间以语言开始的时间。
我唯一真正做过的事情是某个地方出现安全问题,这意味着不应(而不是不能)创建对象。
回答
构造函数的工作是使对象进入可用状态。关于这一点,基本上有两种思想流派。
一组赞成两阶段建设。构造函数仅使对象进入睡眠者状态,在该状态中它拒绝执行任何工作。还有一个添加功能可以进行实际的初始化。
我从不了解这种方法背后的原因。我坚决支持一个阶段的构造,该阶段中的对象已完全初始化,并且在构造后可以使用。
如果一级构造函数无法完全初始化对象,则应抛出该异常。如果无法初始化对象,则必须不允许该对象存在,因此构造函数必须抛出。
回答
请参阅C ++常见问题解答第17.2和17.4节。
总的来说,我发现如果编写构造函数以使它们不会失败,则代码更易于移植和维护结果,而可能失败的代码则放在单独的方法中,该方法返回错误代码并使对象保持惰性。
回答
只要构造函数正确地清理自身,构造函数就抛出异常是合理的。如果遵循RAII范式(资源获取就是初始化),那么构造函数执行有意义的工作是很普遍的。一个编写良好的构造函数如果无法完全初始化,则会依次清除。
回答
是的,如果构造函数无法构建其内部组件之一,则可以选择抛出(并用某种语言声明)显式异常,这是构造函数文档中适当注明的责任。
这不是唯一的选择:它可以完成构造函数并构建一个对象,但是使用方法'isCoherent()'返回false,以便能够发出不连续的状态(在某些情况下可能更可取,因为以避免由于异常而导致执行工作流程的残酷中断)
警告:正如EricSchaefer在他的评论中所说,这可能会给单元测试带来一些复杂性(抛出异常会由于触发条件而增加函数的循环复杂性)
如果由于调用方而失败(如调用方提供的null参数,被调用的构造函数期望使用非null参数),则构造函数无论如何都会抛出未经检查的运行时异常。
回答
在构造过程中引发异常是使代码更复杂的好方法。看起来很简单的事情突然变得困难起来。例如,假设我们有一个堆栈。如何弹出堆栈并返回最高值?好吧,如果堆栈中的对象可以抛出其构造函数(构造临时对象以返回给调用者),则无法保证不会丢失数据(递减堆栈指针,请使用value中的copy构造函数构造返回值堆栈,它抛出,现在有一个堆栈只是失去了一个项目)!这就是为什么std :: stack :: pop不返回值,而我们必须调用std :: stack :: top的原因。
此处很好地描述了此问题,请检查项目10,编写异常安全代码。
回答
如果我们无法创建有效的对象,则绝对应该从构造函数中引发异常。这使我们可以在类中提供适当的不变式。
在实践中,我们可能必须非常小心。请记住,在C ++中,不会调用析构函数,因此,如果在分配资源后抛出该异常,则需要格外小心以正确处理它!
该页面对C ++的情况进行了详尽的讨论。
回答
从Java的角度严格来讲,任何时候使用非法值初始化构造函数时,它都应该引发异常。这样,它就不会在不良状态下构建。
回答
对我来说,这是一个有点哲学性的设计决定。
从ctor时间开始,只要实例存在就一直有效,这是非常好的。在许多不平凡的情况下,如果无法进行内存/资源分配,则可能需要从ctor引发异常。
其他一些方法是init()方法,它本身具有一些问题。其中之一是确保init()实际上被调用。
一种变体是使用惰性方法在首次调用访问器/更改器时自动调用init(),但这需要任何潜在的调用者都必须担心该对象是否有效。 (与"它的存在,因此它是有效的哲学"相对)。
我也看到了各种建议的设计模式来解决这个问题。例如能够通过ctor创建初始对象,但必须调用init()来使用包含加速器/变量的已初始化初始化对象。
每种方法都有其起伏。我已经成功使用了所有这些。如果我们从创建对象开始就不准备使用它们,那么我建议我们使用大量的断言或者异常来确保用户在init()之前不进行交互。
附录
我是从C ++程序员的角度写的。我还假定我们正确使用了RAII惯用语来处理抛出异常时释放的资源。
回答
OO中通常的约定是对象方法确实起作用。
因此,作为一个花招,永远不要从构造函数/初始化中返回僵尸对象。
僵尸无法正常工作,可能缺少内部组件。只是一个空指针异常等待发生。
多年前,我首先在Objective C中制作了僵尸。
像所有经验法则一样,有一个"例外"。
特定接口可能完全有合同规定:
存在一种允许初始化异常的方法"初始化"。
除非调用了initialize,否则补充此接口的对象可能无法正确响应除属性设置器之外的任何调用。在引导过程中,我将此用于OO操作系统中的设备驱动程序,并且它是可行的。
回答
通常,我们不需要僵尸对象。在像Smalltalk这样的语言中,变得变得有些混乱,但是过度使用成为不好的风格。成为对象可以将它原位转换为另一个对象,因此不需要信封包装器(Advanced C ++)或者策略模式(GOF)。
- 致命异常不是错,我们无法阻止它们,也无法明智地清除它们。
- 骨头异常是我们自己的糟糕错误,我们可以阻止它们,因此它们是代码中的错误。
- 令人讨厌的异常是不幸的设计决策的结果。令人讨厌的异常是在完全非异常的情况下引发的,因此必须始终捕获和处理。
- 最后,外来异常似乎有点像令人烦恼的异常,只是它们不是不幸的设计选择的结果。相反,它们是不整洁的外部现实影响我们美观,清晰的程序逻辑的结果。
埃里克·利珀特(Eric Lippert)说,有4种例外。
构造函数永远不要自己抛出致命异常,但是它执行的代码可能会导致致命异常。诸如"内存不足"之类的东西是我们无法控制的,但是如果它发生在构造函数中,嘿,它会发生。
骨头异常不应在任何代码中发生,因此是正确的。
构造函数不应该抛出令人讨厌的异常(例如Int32.Parse()这样的异常),因为它们没有非异常情况。
回答
最后,应该避免外来异常,但是如果我们在构造函数中执行的操作取决于外部环境(例如网络或者文件系统),则抛出异常是适当的。
我无法在Objective-C中解决最佳实践,但是在C ++中,构造函数可以抛出异常。尤其是因为没有其他方法可以确保在不诉诸isOK()方法的情况下报告构造过程中遇到的异常情况。
回答
函数try块功能是专门为支持构造函数成员初始化中的失败而设计的(尽管它也可以用于常规函数)。这是修改或者丰富将抛出的异常信息的唯一方法。但是由于其最初的设计目的(在构造函数中使用),它不允许空的catch()子句吞下该异常。
当构造器无法完成所述对象的构造时,应抛出异常。
例如,如果构造函数应分配1024 KB的ram,但这样做却失败,则应引发异常,这样构造函数的调用者便知道该对象尚未准备就绪,可能会出现错误。需要修复的地方。
回答
半初始化和半死的对象只会引起问题,因为调用者实际上是无法知道的。我宁愿让我的构造函数在出现问题时抛出错误,而不是不得不依赖于编程来运行对isOK()函数的调用,该函数返回true或者false。
如果无法在构造函数中初始化对象,则会引发异常,其中一个示例是非法参数。
回答
作为一般经验法则,应始终尽快抛出异常,因为当问题的根源更接近于表示出问题的方法时,它使调试更加容易。
我只是在学习Objective C,所以我不能真正从经验谈起,但是我确实在Apple的文档中读到了这一点。
http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_6.html
回答
它不仅会告诉我们如何处理我们提出的问题,而且还可以很好地说明问题。
通常,将对象初始化与构造工作区分开来并不会获得任何好处。 RAII是正确的,对构造函数的成功调用应该导致完全初始化的活动对象,否则它应该失败,并且任何代码路径中任何位置的所有失败都应始终引发异常。通过使用单独的init()方法,除了在某种程度上增加复杂性之外,我们一无所获。 ctor合同应该是返回一个有效的功能对象,或者是它自己清除并抛出。
考虑一下,如果实现单独的init方法,则仍然必须调用它。它仍然有可能引发异常,仍然必须对其进行处理,并且无论如何实际上它们总是必须在构造函数之后立即调用,除非现在我们有4种可能的对象状态,而不是2种(IE,构造,初始化,未初始化,失败而只是有效和不存在)。
回答
无论如何,在25年的OO开发案例中,我似乎遇到了一个单独的init方法会"解决一些问题"的设计缺陷。如果现在不需要对象,则现在不应该构造它;如果现在需要它,则需要对其进行初始化。始终应该遵循KISS的原则,以及任何接口的行为,状态和API应该反映对象的功能而不是如何做到的简单概念,客户端代码甚至不应该知道对象具有任何种类内部状态需要初始化,因此初始化后模式违反了这一原理。
回答
通过使用工厂或者工厂方法来创建所有对象,可以避免无效的对象,而不会引发构造函数的异常。如果创建对象可以创建一个对象,则创建方法应该返回该对象;如果不能创建对象,则返回null。我们在处理类用户中的构造错误时会失去一点灵活性,因为返回null不会告诉我们对象创建过程中出了什么问题。但这也避免了每次请求一个对象时都增加多个异常处理程序的复杂性,以及避免捕获不应处理的异常的风险。
段落数量不匹配