信号处理以确保在 C++ 中调用析构函数

Posted

技术标签:

【中文标题】信号处理以确保在 C++ 中调用析构函数【英文标题】:Signal Handling to Ensure Destructors are Called in C++ 【发布时间】:2019-02-28 04:31:47 【问题描述】:

我有一个带有构造函数和析构函数的类。让我们将该类称为 Student。

假设有一些代码


    //some code
    Student student;
    //signal , may be ctrl+c or any other signal


我想要的是当信号被抛出时,我仍然希望调用学生对象的析构函数。

我有一些信号的基本知识,但我不知道如何做到这一点。

我知道这可能是特定于 o/s 的;我正在尝试 Linux。

【问题讨论】:

@Scheff 信号处理是标准的一部分。 【参考方案1】:

这在 C++ 中不容易做到。

您需要handle the signal。处理程序需要通知程序有关事件。程序需要通过干净地关闭来做出反应(这通常通过抛出异常来完成;当然,您的程序必须行使异常安全性才能使其正常工作)。

问题在于通知-反应顺序。没有好的方法可以做到这一点。根据标准,唯一信号处理者被允许做的事情是设置一个sig_atomic_t 类型的全局变量并返回。因此,程序必须定期检查该变量的值,并在其值更改时做出反应。

某些操作系统可能允许在异常处理程序中执行更多安全操作。然而,抛出 C++ 异常通常不是其中之一。信号本质上是异步的。如果一个对象在构造过程中被传递,抛出异常可能会导致其状态不一致。

因此,唯一的方法就是在程序的任何地方插入检查,或者如果它有一个集中的 IO 或事件循环,则在此处插入检查。

【讨论】:

一种常用的技术是为自己打开一个管道,信号处理器将一个字节写入其中,主事件循环读取.. @JesperJuh 这比设置和检查全局更好吗? 这样更好,因为使用全局变量,您必须忙循环来不断检查它。使用管道,您可以在等待管道准备好读取的同时保持阻塞在 selectpollepoll 等。 @JesperJuhl 不,我没有。我检查 常规 IO 操作之后,而不是在繁忙的等待循环中。【参考方案2】:

您也可以根据标准调用std::quick_exit。您需要使用std::at_quick_exit 注册一些清理功能。举个例子:

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <set>
#include <signal.h>
#include <unistd.h>

using namespace std;

class K

public:
    K(const char* _name)
    : name(_name)
     cout << "K() for " << name << endl; registered.emplace(this); 
    ~K()
     cout << "~K() for " << name << endl; registered.erase(this); 

    static void cleanup()
    
        for (auto i: registered) 
            i->~K();
        
    

private:
    const char* name= nullptr;
    static set<K*> registered;
;
set<K*> K::registered;

extern "C"
void signalHandler(int sig, siginfo_t* info, void* context)

    if (sig == SIGKILL || sig == SIGTERM || sig == SIGINT) 
        quick_exit(1);
    
    return;


K k1("global k1");

int main(int argc, const char* argv[])

    struct sigaction act;
    memset(&act, 0, sizeof(act));
    act.sa_sigaction= signalHandler;
    act.sa_flags= SA_SIGINFO;
    sigemptyset(&act.sa_mask);
    for (int i= 0; i < 64; i++) 
        sigaction(i, &act, nullptr);
    

    K k2("local k2");
    at_quick_exit(K::cleanup);
    for (int i= 0; i < 10; i++) 
        cout << "." << flush;
        sleep(1);
    
    cout << "Normal exit" << endl;

    return 0;

【讨论】:

请注意,set 不会保留取消初始化的顺序,您可以使用列表。

以上是关于信号处理以确保在 C++ 中调用析构函数的主要内容,如果未能解决你的问题,请参考以下文章

C++如何自动调用析构函数?

C++ 堆栈分配对象,显式析构函数调用

c++析构函数需要异常处理吗?如需要实现有何要求?

二叉搜索树析构函数

为啥C++里面,析构函数会被调用两次

在 C++ 中抛出后会调用析构函数吗?