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

时间:2009-11-13 21:19:42

标签: c++ signals destructor

我有一个析构函数执行一些必要的清理(它会杀死进程)。即使将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

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

6 个答案:

答案 0 :(得分:5)

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

如果您将其切换为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);

答案 1 :(得分:2)

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

答案 2 :(得分:2)

事实证明,这样做是一个非常糟糕的主意。奇怪的事情发生了很多。

发生了什么

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处于不一致状态时调用处理程序的任何问题。

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:0)

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

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

instance_ = 0;

答案 5 :(得分:0)

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