构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)
在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?
当前回答
如果无法创建有效对象,则绝对应该从构造函数抛出异常。这允许您在类中提供适当的不变量。
在实践中,你可能必须非常小心。记住,在c++中,析构函数不会被调用,所以如果你在分配资源后抛出,你需要非常小心地正确处理它!
本页对c++中的情况进行了全面的讨论。
其他回答
Using factories or factory methods for all object creation, you can avoid invalid objects without throwing exceptions from constructors. The creation method should return the requested object if it's able to create one, or null if it's not. You lose a little bit of flexibility in handling construction errors in the user of a class, because returning null doesn't tell you what went wrong in the object creation. But it also avoids adding the complexity of multiple exception handlers every time you request an object, and the risk of catching exceptions you shouldn't handle.
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.
}
}
我不能说明Objective-C中的最佳实践,但在c++中,构造函数抛出异常是可以的。特别是没有其他方法可以确保在不调用isOK()方法的情况下报告构造过程中遇到的异常情况。
函数try块特性是专门为支持构造函数成员初始化失败而设计的(尽管它也可以用于常规函数)。这是修改或丰富将要抛出的异常信息的唯一方法。但由于其最初的设计目的(在构造函数中使用),它不允许空catch()子句包含异常。
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.
由于部分创建的类可能导致的所有麻烦,我认为永远不会。
如果需要在构造过程中验证某些内容,请将构造函数设为私有并定义一个公共静态工厂方法。如果某些东西无效,该方法可以抛出。但是如果一切都检查出来了,它就调用构造函数,保证不会抛出。