构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
构造函数何时抛出异常是正确的?(或者在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++常见问题解答第17.2和17.4节。
一般来说,我发现如果构造函数被编写,那么它们就不会失败,那么移植和维护结果的代码就会更容易,而可能失败的代码则放在一个单独的方法中,该方法返回错误代码并使对象处于惰性状态。
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。
OP的问题有一个“语言不可知论”的标签…对于所有语言/情况,这个问题不能以同样的方式安全地回答。
下面的c#示例的类层次结构抛出了类B的构造函数,跳过了对类A的IDisposeable的立即调用。在main的使用退出时进行处置,跳过类A资源的显式处置。
例如,如果类A在构造时创建了一个套接字,连接到一个网络资源,在使用块之后可能仍然是这种情况(一个相对隐藏的异常)。
class A : IDisposable
{
public A()
{
Console.WriteLine("Initialize A's resources.");
}
public void Dispose()
{
Console.WriteLine("Dispose A's resources.");
}
}
class B : A, IDisposable
{
public B()
{
Console.WriteLine("Initialize B's resources.");
throw new Exception("B construction failure: B can cleanup anything before throwing so this is not a worry.");
}
public new void Dispose()
{
Console.WriteLine("Dispose B's resources.");
base.Dispose();
}
}
class C : B, IDisposable
{
public C()
{
Console.WriteLine("Initialize C's resources. Not called because B throws during construction. C's resources not a worry.");
}
public new void Dispose()
{
Console.WriteLine("Dispose C's resources.");
base.Dispose();
}
}
class Program
{
static void Main(string[] args)
{
try
{
using (C c = new C())
{
}
}
catch
{
}
// Resource's allocated by c's "A" not explicitly disposed.
}
}