3

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

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

4

6 回答 6

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);
于 2009-11-13T21:35:14.493 回答
2

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

于 2009-11-13T21:23:02.240 回答
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 处于不一致状态时调用处理程序的任何问题。

于 2009-11-16T16:42:07.203 回答
1

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

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

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

于 2009-11-14T07:02:44.843 回答
0

另一种方法是动态分配单例(在首次使用时或在主中),并将delete其用于清理。

嗯。我猜你的 PidManagerPtr 实际上指向一个动态分配的对象......但是 boost::shared_ptr 实际上并没有在重新分配时清理吗?所以应该足够了:

instance_ = 0;

?

于 2009-11-13T21:22:22.963 回答
0

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

于 2009-11-13T21:29:26.703 回答