在 C++ 中,初始化可能涉及构造后的多个方法调用,但这并不违反 RAII。使对象进入自洽的有效状态是构造函数的责任——特别是在可以安全调用析构函数或任何其他方法的状态。根据您的要求,这可能意味着将一些变量设置为零。
那么为什么要对 RAII 大惊小怪呢?好吧,RAII(Resource Allocation Is Initialisation)的原点与异常安全有关。
当一个函数提前退出时,特别是由于抛出异常,调用析构函数很重要——释放资源(例如堆内存和文件句柄)。C++ 要求在变量因此原因超出范围时自动调用析构函数。但是,异常抛出可能随时发生,至少在原则上是这样。你怎么能确定你想要释放的资源已经被分配了呢?如果您不确定该分配是否已经完成,您可以使用标志或空句柄或类似的,以便您可以在析构函数中进行测试,但是您如何确定该标志已被初始化?
基本上,为了能够安全地调用析构函数,C++ 需要知道对象已经被初始化。这是由构造函数处理的。但这只会导致另一个微妙的问题——如果在构造函数内部发生异常抛出会发生什么。
长话短说 - 只有在构造函数成功完成后,C++ 才会对析构函数调用负责。如果构造函数因异常抛出而提前退出,则不会调用析构函数 - 如果在构造函数中未仔细处理异常(或证明是不可能的),则可能会发生内存和/或资源泄漏。
无论如何,名称的由来只是除非构造函数完成,否则不认为资源已被获取,因此当且仅当构造函数成功完成时才应该释放资源(通过调用析构函数)。
不过,这是一个简单的模型,名称可能会产生误导。在实践中,可以通过任何方法获取资源,并且语言对于哪些活动是初始化哪些不是初始化没有固定的想法。重要的是,如果在尴尬的时刻抛出异常并且结果调用了对象析构函数,它应该始终能够准确地确定需要什么清理——释放哪些资源。
构造一个自洽状态的对象并不是一件难事——它可能只需要将一些成员变量设置为零。事实上,有些人强烈建议您在大多数类中仅执行此操作,原因是将一些 POD 成员变量设置为零不会触发异常抛出 - 您不必担心捕获和重新抛出异常以及清理那个半构造的物体。
每个具有析构函数的标准库类都将满足该自洽状态要求。只有“普通旧数据”类型(例如int
)根据定义没有析构函数(嗯,至少没有有用的析构函数)不实现 RAII,至少对于它们自己没有。