[英]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.