C++中,静态对象构造函数中的异常会绕过先前静态对象的析构函数

C++, an exception in the constructor of a static object bypasses destructors of prior static objects

本文关键字:对象 静态 析构函数 构造函数 C++ 异常      更新时间:2023-10-16

我正在研究如何添加C++异常处理来处理现有实时应用程序中的运行时错误。我从封装硬件系统组件驱动程序的对象的构建失败开始,例如位于Raspberry Pi平台SPI总线上的电源监控微控制器。

根据RAII原则,我必须在它们的构造函数中完全初始化这些对象,如果系统资源不可用,例如SPI驱动程序没有加载,这会导致失败的可能性。由于构造函数没有返回值,所以我必须使用异常来处理此类失败。

这些硬件驱动程序对象是静态的(文件范围(。为什么?因此,它们的构造函数会被自动调用,更重要的是,它们的析构函数会在程序退出时被自动调用。此外,我需要从主程序文件中的任何位置获取对象,因为它们代表全局硬件资源。

我并不真正关心如何处理异常(我可以在抛出之前发出有用的错误信息(,但我确实关心程序是否正确终止。

发生的情况是,如果静态分配对象的构造函数失败并抛出异常,则不会调用在失败对象之前的其他静态对象的析构函数。我已经在一个最小的测试台上进行了测试。类Apple、Pear和Orange除了构造函数和析构函数之外什么都没有,它们向stdout声明自己,只是Orange的构造函数随后抛出了一个异常。在主文件中,我按顺序定义了Apple、Pear和Orange的一个静态实例。构造函数在程序执行时被调用,Orange抛出异常,程序结束时不调用Apple和Pear的析构函数。

我在这里错过了什么?

在对类似问题的回答中,例如556655,人们建议:-不在构造函数中引发异常。嗯?-有一个单独的初始化方法,在构造后称为"手动",用于执行任何可能失败的操作。那么RAII呢?(顺便说一句,这就是我现在的情况,没有例外(。-将静态对象更改为指针,并使用新运算符"手动"调用构造函数。然后,在发生故障时,我必须设法调用正确的析构函数,我希望使用异常可以避免这种情况。-将每个静态对象封装在另一个具有访问器函数的对象中,以获得对它的引用。显然,只有在第一次对外部对象调用访问器时,才会调用内部对象的构造函数,这将使我能够捕获异常,据推测,这将导致整洁的退出。这看起来像是一个可怕的拼凑。

回想一下,我不需要捕捉异常,程序可以随心所欲地终止。这使我的问题与我发现的其他问题不同。我的问题是,为什么没有为成功构建的静态对象调用析构函数?

格雷厄姆。

您缺少的是未处理异常。当程序由于未处理的异常而终止时,不能保证调用本地和静态对象的析构函数(我记得它是由实现定义的(。一种解决方案是将这些对象放在一个通用的包装器对象中,在maintry块中创建它,并使指向它的指针可用于当前全局变量的所有任意访问。

草图:

class Drivers
{
friend auto main() -> int;
    // ...
};
namespace impl {
    Drivers* p_drivers;
}  // namespace impl
auto drivers() -> Drivers& { return *impl::p_drivers; }
auto main()
    -> int
{
    try
    {
        Drivers drivers;
        impl::p_drivers = &drivers;
        // ...
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        log_failure( x.what() );
    }
    return EXIT_FAILURE;
}