繁体   English   中英

在信号处理程序中显式调用析构函数

[英]Explicitly calling a destructor in a signal handler

我有一个析构函数执行一些必要的清理(它杀死进程)。 即使将SIGINT发送到程序,它也需要运行。 我的代码目前看起来像:

typedef boost::shared_ptr<PidManager> PidManagerPtr
void PidManager::handler(int sig)
{
  std::cout << "Caught SIGINT\n";
  instance_.~PidManagerPtr();  //PidManager is a singleton
  exit(1);
}
//handler registered in the PidManager constructor

这有效,但似乎有很多警告反对显式调用析构函数。 在这种情况下这是正确的做法,还是有“更正确”的方法呢?

如果该对象是单例,则不需要使用共享指针。 (只有一个!)

如果将其切换为auto_ptr ,则可以在其上调用release() 或者scoped_ptr ,调用reset()

这一切都说,我99%肯定exit()会破坏静态构造的对象。 (这些单身人士往往是。)我所知道的是exit()调用已注册的atexit()函数。

如果您的单身人士没有通过退出自动销毁,那么在您的情况下做的正确的事情是做一个atexit钩子:

void release_singleton(void)
{
    //instance_.release();
    instance_.reset();
}

// in main, probably
atexit(release_singleton);

除非使用placement new构造对象,否则永远不要显式调用析构函数。 将清理代码移动到单独的函数中并调用它。 从析构函数中调用相同的函数。

事实证明,这样做是一个非常糟糕的主意。 奇怪的事情发生的数量是巨大的。

发生了什么事

shared_ptr的use_count为2,进入处理程序。 一个参考是在PidManager本身,另一个是在PidManager的客户端。 调用shared_ptr(~PidManager())的析构函数会将use_count减少一个。 然后,正如GMan所暗示的,当调用exit()时,调用静态初始化的PidManagerPtr instance_的析构函数,将use_count减少为0并导致调用PidManager析构函数。 显然,如果PidManager有多个客户端,use_count就不会降到0,这根本就没有用。

这也提供了一些关于为什么调用instance_.reset()不起作用的提示。 该调用确实将引用计数减少了1.但剩下的引用是PidManager客户端中的shared_ptr。 shared_ptr是一个自动变量,因此它的析构函数不会在exit()中调用。 调用instance_析构函数,但由于它是reset(),因此它不再指向PidManager实例。

解决方案

我完全放弃了使用shared_ptrs并决定改用Meyers Singleton。 现在我的代码看起来像这样:

void handler(int sig)
{
     exit(1);
}

typedef PidManager * PidManagerPtr
PidManagerPtr PidManager::instance()
{
    static PidManager instance_;
    static bool handler_registered = false;
    if(!handler_registered)
    {
        signal(SIGINT,handler);
        handler_registered = true;
    }
    return &instance_;
 }

显式调用exit允许静态初始化的PidManager instance_的析构函数运行,因此不需要在处理程序中放置其他清理代码。 这可以很好地避免在PidManager处于不一致状态时调用处理程序的任何问题。

你真的不想在信号处理程序中做很多事情。 最安全的事情就是设置一个标志(例如全局volatile bool),然后让程序的常规事件循环检查每隔一段时间标记一次,如果它已经变为true,则从那里调用清理/关闭例程。

由于信号处理程序与应用程序的其余部分异步运行,因此从信号处理程序内部执行的操作远不够安全 - 您可能想要与之交互的任何数据可能处于不一致状态。 (并且你不允许使用信号处理程序中的互斥锁或其他同步 - 信号非常邪恶)

但是,如果您不喜欢必须始终轮询布尔值,那么您可以在信号处理程序(至少在大多数操作系统中)中执行的另一项操作是在套接字上发送一个字节。 所以你可以提前设置一个socketpair(),并在套接字对的另一端有正常的事件循环select()(或其他); 当它在该套接字上接收到一个字节时,它知道你的信号处理程序必须已经发送了该字节,因此是时候清理它了。

另一种方法可能是动态分配单例(首次使用或主要),并将其delete以进行清理。

呃。 我猜你的PidManagerPtr实际上指的是一个动态分配的对象...但是不是boost :: shared_ptr实际上是在清理重新分配? 所以它应该足够:

instance_ = 0;

只需在shared_ptr上调用reset(),它就会删除你的实例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM