Exception 构造函数何时抛出异常是正确的?

Exception 构造函数何时抛出异常是正确的?,exception,language-agnostic,constructor,Exception,Language Agnostic,Constructor,构造函数何时抛出异常是正确的?(或者在目标C的情况下:初始者何时返回零?) 在我看来,如果对象不完整,构造函数应该失败——从而拒绝创建对象。也就是说,构造函数应该与其调用者有一个契约,以提供一个函数和工作对象,在这个对象上可以有意义地调用方法?这合理吗?因为一个部分创建的类可能会带来很多麻烦,我认为永远不会 如果需要在构造期间验证某些内容,请将构造函数设为私有,并定义公共静态工厂方法。如果某个内容无效,该方法可以抛出。但如果一切正常,它会调用构造函数,而构造函数保证不会抛出。这总是非常狡猾,尤其

构造函数何时抛出异常是正确的?(或者在目标C的情况下:初始者何时返回零?)


在我看来,如果对象不完整,构造函数应该失败——从而拒绝创建对象。也就是说,构造函数应该与其调用者有一个契约,以提供一个函数和工作对象,在这个对象上可以有意义地调用方法?这合理吗?

因为一个部分创建的类可能会带来很多麻烦,我认为永远不会


如果需要在构造期间验证某些内容,请将构造函数设为私有,并定义公共静态工厂方法。如果某个内容无效,该方法可以抛出。但如果一切正常,它会调用构造函数,而构造函数保证不会抛出。

这总是非常狡猾,尤其是在构造函数中分配资源时;根据您的语言,不会调用析构函数,因此需要手动清理。这取决于对象的生命周期在您的语言中是如何开始的


我唯一一次真正做到这一点是当某个地方出现安全问题时,这意味着不应该而不是不能创建对象。

构造函数的任务是使对象进入可用状态。关于这一点,基本上有两个学派

一个团体赞成两阶段建设。构造函数只是将对象带入一种睡眠状态,在这种状态下它拒绝做任何工作。还有一个额外的函数用于实际初始化

我一直不明白这种方法背后的原因。我坚定地支持一阶段构造,即对象在构造后完全初始化和可用

如果无法完全初始化对象,单阶段构造函数应该抛出。如果对象不能初始化,则不允许存在,因此构造函数必须抛出.< /p>

见C++ FAQ章节和.<


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

构造函数抛出异常是合理的,只要它正确地清理了自身。如果您遵循范例(资源获取是初始化),那么构造函数执行有意义的工作是非常常见的;如果无法完全初始化,一个编写良好的构造函数将在其自身之后进行清理。

是的,如果构造函数未能构建其内部的一个部分,则可以选择抛出(并用特定语言声明)一个,并在构造函数文档中适当注明

这不是唯一的选项:它可以完成构造函数并构建对象,但方法“isCoherent()”返回false,以便能够发出不一致状态的信号(在某些情况下可能更可取,以避免异常导致执行工作流的严重中断)
警告:正如EricSchaefer在他的评论中所说,这可能会给单元测试带来一些复杂性(由于触发函数的条件,抛出会增加函数的可用性)


如果由于调用者而失败(如调用者提供的null参数,被调用的构造函数需要一个非null参数),那么构造函数无论如何都会抛出一个未经检查的运行时异常。

在构造过程中抛出异常是使代码更加复杂的一个好方法。看似简单的事情突然变得困难起来。例如,假设您有一个堆栈。如何弹出堆栈并返回最大值?好吧,如果堆栈中的对象可以抛出它们的构造函数(构造临时对象以返回调用方),就不能保证不会丢失数据(减少堆栈指针,使用堆栈中值的复制构造函数构造返回值,它抛出,现在有一个刚丢失一项的堆栈)!这就是为什么std::stack::pop不返回值,您必须调用std::stack::top


这个问题描述得很好,请检查第10项,编写异常安全代码。

如果无法创建有效对象,则绝对应该从构造函数引发异常。这允许您在类中提供适当的不变量

实际上,你可能必须非常小心。请记住,在C++中,析构函数将不被调用,所以如果在分配资源之后抛出,则需要非常小心地处理它。p>

对C++中的情况进行了深入的讨论。

严格地从java的观点讲,任何时候初始化具有非法值的构造函数时,都应该抛出异常。这样它就不会在不好的状态下构建。

对我来说,这是一个有点哲学的设计决策

从一开始,只要实例存在,它们就有效,这是非常好的。对于许多非平凡的情况,如果无法进行内存/资源分配,这可能需要从ctor抛出异常

其他一些方法是init()方法,它本身也有一些问题。其中之一是确保实际调用init()

一个变体使用惰性方法在第一次调用访问器/变异器时自动调用init(),但这要求任何潜在的调用方都必须担心对象是否有效。(与“它存在,因此它是有效的哲学”相反)

我也见过各种各样的设计模式来处理这个问题。例如,可以通过ctor创建初始对象,但必须调用init()才能使用ACCESOR/MUTATIONS获得包含的初始化对象

每种方法都有其起伏;我已经成功地使用了所有这些。如果您没有准备好使用对象f
public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(dateOfBirth));
        }

        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }
}
public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    public Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public void Validate()
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(Name));
        }

        if (DateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }
    }
}
public class Person
{
    public string Name { get; }
    public DateTime DateOfBirth { get; }

    private Person(string name, DateTime dateOfBirth)
    {
        this.Name = name;
        this.DateOfBirth = dateOfBirth;
    }

    public static Person Create(
        string name,
        DateTime dateOfBirth)
    {
        if (string.IsNullOrWhitespace(Name))
        {
            throw new ArgumentException(nameof(name));
        }

        if (dateOfBirth > DateTime.UtcNow) // side note: bad use of DateTime.UtcNow
        {
            throw new ArgumentOutOfRangeException(nameof(DateOfBirth));
        }

        return new Person(name, dateOfBirth);
    }
}
public class RestApiClient
{
    public RestApiClient(HttpClient httpClient)
    {
        this.httpClient = new httpClient;
    }

    public async Task<RestApiClient> Create(string username, string password)
    {
        if (username == null)
        {
            throw new ArgumentNullException(nameof(username));
        }

        if (password == null)
        {
            throw new ArgumentNullException(nameof(password));
        }

        var basicAuthBytes = Encoding.ASCII.GetBytes($"{username}:{password}");
        var basicAuthValue = Convert.ToBase64String(basicAuthBytes);

        var authenticationHttpClient = new HttpClient
        {
            BaseUri = new Uri("https://auth.example.io"),
            DefaultRequestHeaders = {
                Authentication = new AuthenticationHeaderValue("Basic", basicAuthValue)
            }
        };

        using (authenticationHttpClient)
        {
            var response = await httpClient.GetAsync("login");
            var content = response.Content.ReadAsStringAsync();
            var authToken = content;
            var restApiHttpClient = new HttpClient
            {
                BaseUri = new Uri("https://api.example.io"), // notice this differs from the auth uri
                DefaultRequestHeaders = {
                    Authentication = new AuthenticationHeaderValue("Bearer", authToken)
                }
            };

            return new RestApiClient(restApiHttpClient);
        }
    }
}
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.
    }
}