std::函数作为sighandler_t

std::function as sighandler_t

本文关键字:sighandler 函数 std      更新时间:2023-10-16

如何指定lambda,std::bind result或任何其他std::function作为unix信号函数的参数?

我正在尝试以下操作

std::function<void(int)> handler1 = std::bind(&cancellation_token::cancel, &c);
std::function<void(int)> handler2 = [&c](int) { c.cancel(); };

但它不起作用,因为两者都

handler1.target<void(int)>()

handler2.target<void(int)>()

return null

如果我使用空闲函数指针初始化处理程序,它有效

void foo(int) { ... }
std::function<void(int)> handler = foo;

但这绝对没用。我需要捕获一些局部变量,所以我需要绑定或 lambda。

实际上我理解为什么它不起作用。文档说target函数返回一个指向存储函数的指针,如果target_type() == typeid(T),否则为空指针。我不明白如何让它工作。

有什么建议吗?

由于它是由 bind 或 lambda 使用捕获的数据构造的,因此您无法将其转换为自由函数,因为target函数通过 typeid 工作,std::function将其保存在运行时中,而不是用于模板化functionT 类型。对于std::bind,它将是一些库类型,对于 lambda,它将是一些未命名的类型。

您可以使用类似调度程序的方法,通过地图将信号编号与std::function相关联。

你只需要一个映射来保存std::function可以从自由函数访问:

std::unordered_map<int, std::function<void(int)>> signalHandlers;

还有一个通用处理程序(free函数)将信号号映射到函数:

void dispatcher(int signal) {
  // this will call a previously saved function
  signalHandlers.at(signal)(signal);
}

实现示例

主.cpp

#include <iostream>
#include <thread>
#include <csignal>
#include "cppsignal.hpp"
int main() {
  bool stop = false;
  // set a handler as easy as this
  CppSignal::setHandler(SIGINT, [&stop] (int) { stop = true; });
  while (!stop) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }
  std::cout << "Bye" << std::endl;
  return 0;
}

CPP信号.cpp

#include <cstring> // strsignal
#include <csignal>
#include <string>
#include <stdexcept>
#include <unordered_map>
#include <mutex>
#include "signal.hpp"
namespace CppSignal {
std::timed_mutex signalHandlersMutex;
std::unordered_map<int, std::function<void(int)>> signalHandlers;
// generic handler (free function) to set as a handler for any signal
void dispatcher(int signal) {
  std::unique_lock<std::timed_mutex> lock(signalHandlersMutex, std::defer_lock);
  if (!lock.try_lock_for(std::chrono::seconds(1))) {
    // unable to get the lock. should be a strange case
    return;
  }
  auto it = signalHandlers.find(signal);
  if (it != signalHandlers.end()) {
    it->second(signal);
  }
}
void registerHandler(int signal, const std::function<void(int)>& handler) {
  std::lock_guard<std::timed_mutex> lock(signalHandlersMutex);
  signalHandlers.emplace(signal, handler);
}
// this is the only method you will use
void setHandler(int signal, const std::function<void(int)>& handler, int flags) {
  // configure sigaction structure
  struct sigaction action;
  if (sigfillset(&action.sa_mask) == -1) {
    throw std::runtime_error("sigfillset failed");
  }
  action.sa_flags = flags;
  action.sa_handler = dispatcher;
  // set handler for the signal
  if (sigaction(signal, &action, nullptr) == -1 && signal < __SIGRTMIN) {
    throw std::runtime_error("Fail at configuring handler for signal: " + std::string(strsignal(signal)));
  }
  registerHandler(signal, handler);
}
}

CPP信号.hpp

#ifndef __CPPSIGNAL_HPP
#define __CPPSIGNAL_HPP
#include <functional>
namespace CppSignal {
void setHandler(int signal, const std::function<void(int)>& handler, int flags=0);
}
#endif

sighandler_t被定义为指向具有以下定义的函数的指针:

void func(int);

由于 std::bind 和 lambda 返回函子,因此不可能直接将它们用作信号处理程序。作为一种解决方法,您可以使用自己的包装器函数,例如

class SignalHandlerBase
{
public:
  virtual void operator(int) = 0;
};
template <class T>
class SignalHandler : public SignalHandlerBase
{
  T t;
public:
  SignalHandler(T _t) : t(_t) { }
  void operator(int i)
  {
    t(i);
  } 
};
class SignalManager
{
  int sig;
  SignalHandlerBase *shb;
  static void handlerFunction(int i)
  {
    shb(i);
  }
public:
  SignalManager(int signal) : sig(signal), shb(nullptr) { signal(signal, &handlerFunction); }
  template <class T>
  void installHandler(T t)
  {
    delete shb;
    shb = new SignalHandler<T>(t);
  }
};

使用SignalManager的全局实例来管理单个信号

C++11 1.9 [intro.execution]/6:

当抽象机器的处理因接收信号而中断时,对象的值 都不是

  • 类型为 volatile std::sig_atomic_t 也不是

  • 无锁原子对象 (29.4)

在执行信号处理程序期间未指定,并且任何 对象不在这两个中 处理程序修改的两个类别变为未定义。

在信号处理程序中实际可以移植的唯一操作是更改类型为 volatile std::sig_atomic_t 或无锁std::atomic的标志的值(请注意,并非所有std::atomic对象都是无锁的)。然后,非信号处理代码可以轮询该标志以响应信号的发生。

N3787 有一些关于如何修复 C++11 基本上中断信号处理程序的概念的有趣讨论。