1

I have simplified my example for an easier explanation. I am writing an application that counts to 100 but at any given time I allow the user to cancel the program by entering ctrl+c through the keyboard.

What seemingly started as a simple program quickly became complicated based on my lack of knowledge on function pointers. This is what I'm attempting to do:

  1. Capture the SIGINT signal when ctrl+c is pressed.
  2. Once captured, call a member function that shuts down a third-party resource.

The catch is that unlike the two examples that Michael Haidl and Grijesh Chauhan give on capturing SIGINT, I am not permitted to store any global variables. The ideal scenario is one in which all variables and function calls related to signal() are encapsulated within a class of mine.

Here's my modified attempt based on Haidl and Grijesh's code:

#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;
}

As you can see, I'd like the code to simply count to 100 and exit if all goes well. But if the user calls ctrl+c then the class should handle SIGINT, call the external library for cleanup, and the for loop will exit.

The main problem is that I can't seem to setup the signal() declaration to bind to my instance of MyClass::sig_handler. I even tried casting my member function to std::function to be used by signal(), commented out, but the compiler isn't happy about the fact that C++ function<void(int)> isn't equivalent to the C lang void (*)(int).

Any and all criticism is welcome. I'm not at all tied to what I've written and I clearly don't have a great fundamental understanding of how to use function pointers with member functions.

Max
  • 2,072
  • 5
  • 26
  • 42
  • Signal handles are global. How do you expect it to function if there's more than one instance of `MyClass`? – Yakov Galka May 15 '19 at 00:06
  • good point, there is likely to only be one instance of `MyClass` does this mean I can simply have the `sig_handler` be static? – Max May 15 '19 at 00:11
  • Yes, and have a global pointer pointing to the only instance of `MyClass` that you have. Which also raises the question why you need it to be a class in the first place. – Yakov Galka May 15 '19 at 00:12
  • You can't take the address of a non-static member function because you cannot call a non-static member function without some specific instance of the class. See pointer-to-member-function for more explanation (although you can't use a PTMF as a C callback either). You can use a static member function, although that is morally equivalent to a global variable. – rici May 15 '19 at 01:12
  • Are you allowed to use static class variables? They have global lifetime, if not global scope. – Seva Alekseyev May 15 '19 at 02:00
  • the posted code is C++, not C (they are two different languages) please remove the 'c' tag – user3629249 May 15 '19 at 02:37
  • If you are allowed to use Asio, check out https://stackoverflow.com/questions/42342305/using-boost-asio-to-catch-ctrl-c @Max – pratikpc Mar 01 '21 at 02:41

1 Answers1

4

It is not possible to communicate between the signal handler and the rest of the program using local variables. No parameters are passed into the handler other than the raised signal and the handler returns no value.

The words "global variables" are somewhat ambiguous. People sometimes mean different things depending on context. If your restriction applies only to the global scope, then simply use a volatile sig_atomic_t within some namespace. Or use static member variable, if you so prefer.

If your restriction applies to static storage duration, then you can use a thread local variable instead.

If your restriction applies to all global memory, then your problem is unsolvable using a signal handler. You simply need a global variable of some sort.


If you can rely on POSIX rather than C++ standard, A way to handle SIGINT without globals is to make sure that it is not handled, and block the thread with sigwait. If the call returns SIGINT, then stop the program, otherwise do what you want to do with the signal that was caught.

Of course, this means that the blocking thread doesn't do anything other than wait for signals. You'll need to do the actual work in other thread(s).

Technically though, global memory is probably still used. The use is simply hidden inside system library.


Furthermore, it is not safe to use std::cout within a signal handler. I know that is only an example, but "call the external library for cleanup" is very likely also async signal unsafe.

This can be fixed simply by calling the cleanup outside the for loop rather than inside the handler.


The main problem is that I can't seem to setup the signal() declaration to bind to my instance of MyClass::sig_handler.

That's because signal requires a function pointer (of type void(int)). Non-static member functions cannot be pointed by function pointers. They can only be pointed by member function pointers, which signal doesn't accept.

eerorika
  • 232,697
  • 12
  • 197
  • 326