构造函数何时抛出异常是正确的?(或者在Objective C的情况下:什么情况下init ` er才应该返回nil?)

在我看来,如果对象不完整,构造函数应该失败——因此拒绝创建对象。也就是说,构造函数应该与它的调用者有一个合同,以提供一个函数和工作对象,在哪些方法可以被有意义地调用?这合理吗?


当前回答

请注意,如果您在初始化器中抛出异常,如果任何代码使用[[[MyObj alloc] init] autorelease]模式,那么您将最终泄漏,因为异常将跳过自动释放。

看这个问题:

在init中引发异常时如何防止泄漏?

其他回答

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.

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.
    }
}

如果无法在构造函数中初始化对象,则抛出异常,一个例子是非法参数。

根据一般经验,应该尽可能快地抛出异常,因为当问题的根源接近发出错误信号的方法时,这会使调试变得更容易。

构造函数抛出异常是合理的,只要它正确地清理了自己。如果您遵循RAII范式(资源获取即初始化),那么构造函数通常会做有意义的工作;如果构造函数不能完全初始化,那么编写良好的构造函数将自行清理。

参见c++常见问题解答第17.2和17.4节。

一般来说,我发现如果构造函数被编写,那么它们就不会失败,那么移植和维护结果的代码就会更容易,而可能失败的代码则放在一个单独的方法中,该方法返回错误代码并使对象处于惰性状态。