C ++ перехват Ctrl + C без использования глобальных - PullRequest
1 голос
/ 15 мая 2019

Я упростил мой пример для более простого объяснения.Я пишу приложение, которое рассчитывает на 100, но в любой момент я позволяю пользователю отменить программу, введя ctrl+c с клавиатуры.

То, что казалось простой простой программой, быстро усложнилось из-за моейнедостаток знаний о функциональных указателях.Вот что я пытаюсь сделать:

  1. Захват сигнала SIGINT при нажатии ctrl+c.
  2. После захвата вызовите функцию-член, которая отключает стороннюю систему.ресурс.

Уловка в том, что в отличие от двух примеров, которые Майкл Хайдл и Гриджеш Чаухан дают при захвате SIGINT, мне запрещено хранитьлюбые глобальные переменные.Идеальный сценарий - это сценарий, в котором все переменные и вызовы функций, связанные с signal(), инкапсулированы в моем классе.

Вот моя модифицированная попытка, основанная на Haidl и Grijesh's код:

#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<void(int)> не эквивалентен C lang void (*)(int).

Любая критика приветствуется.Я нисколько не привязан к тому, что написал, и у меня явно нет глубокого фундаментального понимания того, как использовать указатели на функции с функциями-членами.

1 Ответ

4 голосов
/ 15 мая 2019

Невозможно установить связь между обработчиком сигнала и остальной частью программы, используя локальные переменные.Никакие параметры не передаются в обработчик, кроме повышенного сигнала, и обработчик не возвращает значения.

Слова "глобальные переменные" несколько неоднозначны.Люди иногда имеют в виду разные вещи в зависимости от контекста.Если ваше ограничение применяется только к глобальной области, то просто используйте volatile sig_atomic_t в некотором пространстве имен.Или используйте статическую переменную-член, если вы так предпочитаете.

Если ваше ограничение применяется к статической продолжительности хранения, вы можете вместо этого использовать локальную переменную потока.

Если ваше ограничение применяется ко всей глобальной памяти, тогда ваша проблема неразрешима с помощью обработчика сигнала.Вам просто нужна какая-то глобальная переменная.


Если вы можете полагаться на стандарт POSIX, а не на стандарт C ++, то способ обработки SIGINT без глобальных переменных состоит в том, чтобы убедиться, что это не обработано, и заблокируйте нить с помощью sigwait.Если вызов возвращает SIGINT, остановите программу, в противном случае сделайте то, что вы хотите сделать с перехваченным сигналом.

Конечно, это означает, что блокирующий поток не делает ничего, кроме ожиданиядля сигналов.Вам нужно будет выполнить фактическую работу в других потоках.

Технически, хотя глобальная память, вероятно, все еще используется.Использование просто скрыто внутри системной библиотеки.


Более того, использование std::cout в обработчике сигнала небезопасно.Я знаю, что это только пример, но "вызов внешней библиотеки для очистки" очень вероятно, что асинхронный сигнал небезопасен.

Это можно исправить, просто вызвав очистку вне цикла forа не внутри обработчика.


Основная проблема заключается в том, что я не могу настроить объявление signal() для привязки к моему экземпляру MyClass::sig_handler.

Это потому, что signal требует указатель функции (типа void(int)).Нестатические функции-члены не могут указываться указателями функций.Они могут указываться только указателями на функции-члены, которые signal не принимает.

...