【问题标题】:c++ capture ctrl+c without using globalsc++ 在不使用全局变量的情况下捕获 ctrl+c
【发布时间】:2019-10-02 01:11:39
【问题描述】:

为了更容易解释,我已经简化了我的示例。我正在编写一个计数为 100 的应用程序,但在任何给定时间,我都允许用户通过键盘输入 ctrl+c 来取消该程序。

由于我对函数指针缺乏了解,最初看似简单的程序很快变得复杂起来。这就是我正在尝试做的:

  1. 按下ctrl+c时捕获SIGINT信号。
  2. 捕获后,调用一个成员函数来关闭第三方资源。

问题在于,与Michael HaidlGrijesh Chauhan 给出的捕获SIGINT 的两个示例不同,我不允许存储任何全局变量。理想的情况是,与signal() 相关的所有变量和函数调用都封装在我的一个类中。

这是我基于 HaidlGrijesh 的 代码的修改尝试:

#include <thread>
#include <chrono>
#include <functional>
#include <iostream>
#include <signal.h>

class MyClass {
    public:
        volatile sig_atomic_t cancel = 0;
        void sig_handler(int signal) { 
            cancel = true; 
            this->libCancel();
        }   
        void libCancel() { std::cout << "Cancel and cleanup" << std::endl; }
};

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

  MyClass mc; 
  //using std::placeholders::_1;
  //std::function<void(int)> handler = std::bind(&MyClass::sig_handler, mc, _1);
  //signal(SIGINT, handler);
  signal(SIGINT, &mc.sig_handler); // **compiler error** 

  for (int i = 0; !mc.cancel && i < 100; ++i)
  {
      std::cout << i << std::endl;
      std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  return 0;
}

如您所见,我希望代码简单地数到 100 并在一切顺利时退出。但是如果用户调用ctrl+c,那么该类应该处理SIGINT,调用外部库进行清理,for 循环将退出。

主要问题是我似乎无法设置signal() 声明来绑定到我的MyClass::sig_handler 实例。我什至尝试将我的成员函数转换为 std::function 以供 signal() 使用,注释掉了,但编译器对 C++ function&lt;void(int)&gt; 不等同于 C 语言 void (*)(int) 的事实并不满意。

欢迎任何批评。我完全不依赖于我所写的内容,而且对于如何将函数指针与成员函数一起使用,我显然没有很好的基本理解。

【问题讨论】:

  • 信号句柄是全局的。如果MyClass 的实例不止一个,您希望它如何运行?
  • 好点,MyClass 可能只有一个实例,这是否意味着我可以简单地将 sig_handler 设为静态?
  • 是的,并且有一个全局指针指向您拥有的唯一 MyClass 实例。这也提出了一个问题,为什么你首先需要它成为一个类。
  • 你不能获取非静态成员函数的地址,因为你不能在没有类的特定实例的情况下调用非静态成员函数。有关更多解释,请参阅指向成员函数的指针(尽管您也不能将 PTMF 用作 C 回调)。您可以使用静态成员函数,尽管这在道德上等同于全局变量。
  • 是否允许使用静态类变量?它们具有全局生命周期,如果不是全局范围的话。

标签: c++ c lambda function-pointers sigint


【解决方案1】:

无法使用局部变量在信号处理程序和程序的其余部分之间进行通信。除了引发的信号之外,没有任何参数传递给处理程序,并且处理程序不返回任何值。

“全局变量”这个词有些含糊。人们有时会根据上下文表达不同的意思。如果您的限制仅适用于全局范围,则只需在某个命名空间中使用 volatile sig_atomic_t。如果您愿意,也可以使用静态成员变量。

如果您的限制适用于静态存储持续时间,那么您可以改用线程局部变量。

如果您的限制适用于所有全局内存,那么使用信号处理程序将无法解决您的问题。您只需要某种全局变量。


如果您可以依赖 POSIX 而不是 C++ 标准,那么在没有全局变量的情况下处理 SIGINT 的一种方法是确保它被处理,并使用 sigwait 阻塞线程。如果调用返回SIGINT,则停止程序,否则对捕获的信号执行您想要执行的操作。

当然,这意味着阻塞线程除了等待信号不做任何事情。您需要在其他线程中完成实际工作。

不过,从技术上讲,可能仍在使用全局内存。使用只是隐藏在系统库中。


此外,在信号处理程序中使用std::cout 是不安全的。我知道这只是一个例子,但是“调用外部库进行清理”很可能也是异步信号不安全的。

这可以通过在 for 循环外部而不是在处理程序内部调用清理来解决。


主要问题是我似乎无法设置 signal() 声明以绑定到我的 MyClass::sig_handler 实例。

这是因为signal 需要一个函数指针(void(int) 类型)。非静态成员函数不能被函数指针指向。它们只能由成员函数指针指向,signal 不接受。

【讨论】:

  • 赞成。虽然我认为epollkqueue 可以让您通过事件传递上下文。
  • 我最终为类使用了静态成员变量,这成功了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 1970-01-01
  • 1970-01-01
  • 2018-01-14
  • 2014-12-28
相关资源
最近更新 更多