std::function as sighandler_t

Viewed 3027

How to specify lambda, std::bind result or any other std::function as argument for unix signal function?

I'm trying the following

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

but it doesn't work, because both

handler1.target<void(int)>()

and

handler2.target<void(int)>()

return null

It works if I initialize handler with free function pointer

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

but this is absolutely useless. I need to capture some local variables, so I need either bind or lambda.

Actually I understand why it doesn't work. Documentation says that target function returns a pointer to the stored function if target_type() == typeid(T), otherwise a null pointer. I don't understand how to make it work.

Any suggestions?

4 Answers

You can use a dispatcher-like approach associating signal numbers to std::functions through a map.

You just need a map to hold the std::functions accesible from a free function:

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

And a generic handler (free function) to map the signal number to the function:

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

Implementation example

main.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;
}

cppsignal.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);
}

}

cppsignal.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
Related