构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
当前回答
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。
其他回答
我不能说明Objective-C中的最佳实践,但在c++中,构造函数抛出异常是可以的。特别是没有其他方法可以确保在不调用isOK()方法的情况下报告构造过程中遇到的异常情况。
函数try块特性是专门为支持构造函数成员初始化失败而设计的(尽管它也可以用于常规函数)。这是修改或丰富将要抛出的异常信息的唯一方法。但由于其最初的设计目的(在构造函数中使用),它不允许空catch()子句包含异常。
如果你正在编写ui控件(ASPX, WinForms, WPF,…),你应该避免在构造函数中抛出异常,因为设计器(Visual Studio)在创建控件时无法处理它们。了解你的控件生命周期(控件事件),尽可能使用惰性初始化。
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.
}
}
这总是很危险的,特别是当你在构造函数中分配资源时;根据你的语言,析构函数不会被调用,所以你需要手动清理。这取决于在您的语言中对象的生命周期何时开始。
我真正这么做的唯一一次是在某个地方出现了安全问题,这意味着对象不应该,而不是不能,被创建。
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。