Анонимная функция С++
Я пытаюсь использовать функцию signal(int,void(*)(int))
из <csignal>
для обработки исключения SIGFPE с плавающей запятой. Я хотел бы иметь возможность распечатать какую-нибудь полезную диагностику, кроме как только сообщение с сообщением "Исключение с плавающей запятой" или что-то в этом роде. Это означает, что функция, которую я передаю, поскольку обработчик для signal
нуждается в доступе к некоторым данным в моем коде. В этом заключается трение.
Функция должна возвращать void
и принимать только 1 параметр типа int
. Я не могу сделать обработчик функцией-членом моего класса хранения данных, так как тогда тип был бы void(Foo::*)(int)
из-за скрытого указателя this
.
Я думал об использовании lambdas, чтобы попытаться сделать анонимную функцию следующим образом:
void handler(int nSig, Foo data)
{
// do something
}
// snip
Foo data;
signal(SIGFPE, [&](int nSig)->void{handler(nSig,data);});
однако, поскольку лямбда захватывает переменную data
извне, компилятор не позволяет ей быть привязана к указателю на void(*)(int)
(что является позором, поскольку это кажется идеальным для использования лямбдами).
Я мог бы просто сделать data
глобальную переменную, которую можно было бы увидеть в handler
, но я не могу сделать это по понятным причинам.
Итак, мой вопрос: Каков наилучший способ имитации анонимных функций в С++?
Примечание. Я бы предпочел родное решение на С++ и не должен был использовать boost или эквивалент.
Ответы
Ответ 1
В C нет такой функции, как анонимная функция (С++ здесь неактуальна, так как функция должна соблюдать соглашение C-вызова).
Единственное, что вы можете сделать - это глобальные переменные доступа к дроби от обработчика, возможно, глобальные переменные (а не константы, которые были бы точными).
Я советую сделать эти глобальные потоки локальными, чтобы избежать многопоточных проблем, но все равно плохо в том смысле, что глобальные переменные делают более хрупкие приложения.
Как?
Примечание: по мере того, как Люк Дантон терпеливо объяснял мне, сигнал может прерывать любую неатомную деятельность, и, таким образом, чтение из глобальной является безопасным только в том случае, если она является блокирующим атомом (или несколькими другими вещами). К сожалению, std::function
может быть не так, в зависимости от вашей реализации, я все равно оставлю этот код, чтобы объяснить, как это можно сделать , при условии, что обращения std::function
являются атомарными.
Можно создать батут, который будет вызывать состояние с сохранением состояния, изолировать поток и разрешать повторные вызовы.
typedef std::function<void(int)> SignalHandlerType;
extern thread_local ignalHandlerType SignalHandler;
И мы создаем следующий accessor (переданный сигналу):
void handle_signal(int const i) {
if (SignalHandler) { SignalHandler(i); }
}
а также следующий установщик RAII:
class SignalSetter: boost::noncopyable {
public:
SignalSetter(int signal, SignalHandlerType&& sh):
signal(signal), chandler(0), handler(sh)
{
chandler = std::signal(signal, &handle_signal<T>);
swap(SignalHandler, handler);
}
~SignalSetter() {
std::signal(signal, chandler);
swap(SignalHandler, handler);
}
private:
typedef void(*CHandlerType)(int);
int signal;
CHandlerType chandler;
SignalHandlerType handler;
};
Примечание: как глобальная переменная, так и handle_signal
могут быть private
для класса SignalSetter
... но так как std::signal
не...
Ожидаемое использование:
int main(int argc, char* argv[]) {
SignalSetter setter(SIGFPE, [argc, argv]() {
std::cout << argc << ": " << argc << std::endl;
});
// do what you want.
}
Ответ 2
Это действительно хороший вопрос. Дайте понять, что происходит, прежде чем обвинять С++. Просто подумайте о том, как реализованы лямбды.
Самая простая лямбда - это когда данные не захватываются. Если это так, его базовый тип становится простой простой функцией. Например, такая лямбда выглядит следующим образом:
[] (int p0) {}
будет эквивалентом простой функции:
void foo(int p0)
{
}
Это действительно отлично работает, если вы хотите, чтобы лямбда стала указателем на функцию. Например:
#include <string>
#include <csignal>
#include <iostream>
int main()
{
int ret;
signal(SIGINT, [](int signal) {
std::cout << "Got signal " << signal << std::endl;
});
std::cin >> ret;
return ret;
}
Пока все хорошо. Но теперь вы хотите связать некоторые данные с вашим обработчиком сигнала (кстати, код выше undefined поведение вы можете выполнять только безопасный код внутри обработчик сигнала). Итак, вам нужна лямбда:
#include <string>
#include <csignal>
#include <iostream>
struct handler_context {
std::string code;
std::string desc;
};
int main()
{
int ret;
handler_context ctx({ "SIGINT", "Interrupt" });
signal(SIGINT, [&](int signal) {
std::cout << "Got signal " << signal
<< " (" << ctx.code << ": " << ctx.desc
<< ")\n" << std::flush;
});
std::cin >> ret;
return ret;
}
Остановитесь на мгновение о синтаксическом саже С++ lambdas. Не секрет, что вы можете "имитировать" лямбда даже на C или ассемблере. Так как же это будет выглядеть? "Lambda" в стиле C может выглядеть так (это еще С++):
#include <string>
#include <cstdlib>
#include <iostream>
/*
* This is a context associated with our lambda function.
* Some dummy variables, for the sake of example.
*/
struct lambda_captures {
int v0;
int v1;
};
static int lambda_func(int p0, void *ctx) // <-- This is our lambda "function".
{
lambda_captures *captures = (lambda_captures *)ctx;
std::cout << "Got " << p0 << " (ctx: "
<< captures->v0 << ", " << captures->v1
<< ")\n" << std::flush;
return 0;
}
// Below is an example of API function provided to the user that can
// invoke a callback supplied by the user.
static void some_api_function(int (*callback)(int p, void *data), void *data)
{
callback(12345, data);
callback(98765, data);
}
int main()
{
lambda_captures captures;
captures.v0 = 1986;
captures.v1 = 2012;
some_api_function(lambda_func, (void *)&captures);
return EXIT_SUCCESS;
}
Выше - стиль C, С++ имеет тенденцию передавать "контекст" как "this", который всегда является неявным первым аргументом. Если наш API поддерживал передачу "данных" в качестве первого аргумента, мы могли бы применить указатель на преобразование членов (PMF) и написать что-то вроде этого:
#include <string>
#include <cstdlib>
#include <iostream>
struct some_class {
int v0;
int v1;
int func(int p0)
{
std::cout << "Got " << p0 << " (ctx: "
<< v0 << ", " << v1
<< ")\n" << std::flush;
return p0;
}
};
static void some_api_function(int (*callback)(void *data, int p), void *data)
{
callback(data, 12345);
callback(data, 98765);
}
int main()
{
typedef int (*mpf_type)(void *, int);
some_class clazz({ 1986, 2012 }); // <- Note a bit of a Java style :-)
some_api_function((mpf_type)&some_class::func, (void *)&clazz);
return EXIT_SUCCESS;
}
В приведенных выше двух примерах обратите внимание, что "данные" всегда передаются. Это очень важно. Если API, который должен вызывать ваш обратный вызов, не принимает указатель "void *", который каким-то образом передается обратно на ваш обратный вызов, вы не можете связать какой-либо контекст с обратным вызовом. Единственным исключением являются глобальные данные. Например, этот API плохой:
#include <string>
#include <cstdlib>
#include <iostream>
struct lambda_captures {
int v0;
int v1;
};
static int lambda_func(int p0)
{
/*
// WHERE DO WE GET OUR "lambda_captures" OBJECT FROM????
lambda_captures *captures = (lambda_captures *)ctx;
std::cout << "Got " << p0 << " (ctx: "
<< captures->v0 << ", " << captures->v1
<< ")\n" << std::flush;
*/
return 0;
}
// Below is an example of API function provided to the user that can
// invoke a callback supplied by the user.
static void some_api_function(int (*callback)(int p))
{
callback(12345);
callback(98765);
}
int main()
{
lambda_captures captures;
captures.v0 = 1986;
captures.v1 = 2012;
some_api_function(lambda_func /* How do we pass a context??? */);
return EXIT_SUCCESS;
}
Таким образом, старый API-интерфейс аналогичен. Единственный способ обойти эту проблему - фактически превратить ваш "контекст" в глобальную сферу. Тогда функция обработчика сигнала может получить к нему доступ, потому что адрес хорошо известен, например:
#include <string>
#include <cstdlib>
#include <iostream>
struct lambda_captures {
int v0;
int v1;
};
lambda_captures captures({ 1986, 2012 }); // Whoa-la!!!
static int lambda_func(int p0)
{
std::cout << "Got " << p0 << " (ctx: "
<< captures.v0 << ", " << captures.v1
<< ")\n" << std::flush;
return 0;
}
// Below is an example of API function provided to the user that can
// invoke a callback supplied by the user.
static void some_api_function(int (*callback)(int p))
{
callback(12345);
callback(98765);
}
int main()
{
some_api_function(lambda_func);
return EXIT_SUCCESS;
}
Это то, с чем люди должны иметь дело. Не только в случае с сигналами API. Это относится и к другим вещам. Например, обработка обработчика прерываний. Но это низкоуровневое программирование, где вам приходится иметь дело с оборудованием. Конечно, предоставление такого API-интерфейса в пользовательском пространстве было не лучшей идеей. И я расскажу об этом еще раз - в обработчике сигналов есть только небольшой набор вещей. Вы можете вызывать асинхронные сигнальные функции.
Конечно, старый API не уходит в ближайшее время, потому что это фактически стандарт POSIX. Тем не менее, разработчики распознают проблему, и есть более эффективные способы обработки сигналов. В Linux, например, вы можете использовать eventfd
для установки обработчика сигнала, связать его с произвольным контекстом и делать все, что вы хотите в обратном вызове функция.
Во всяком случае, вернемся к лямбде, с которой вы играли. Проблема не в С++, но с сигналами API, которые не дают вам возможности передать контекст, за исключением использования глобальной переменной. Это, как говорится, также работает с лямбдами:
#include <string>
#include <cstdlib>
#include <csignal>
#include <iostream>
struct some_data {
std::string code;
std::string desc;
};
static some_data data({ "SIGING", "Interrupt" });
int main()
{
signal(SIGINT, [](int signal) {
std::cout << "Got " << signal << " (" << data.code << ", "
<< data.desc << ")\n" << std::flush;
});
return EXIT_SUCCESS;
}
Следовательно, нет никакого позора в том, что делает С++ здесь, так как он делает правильную вещь.
Ответ 3
Вы не можете легко создать новую статическую функцию во время выполнения, некоторые JIT-компиляторы libs могут это сделать.
Если вам нужно только разумное количество указателей, вы можете создать пул статических функций, специализируясь на шаблоне.
Таким образом, самый простой способ - включить С++-функторы статической функцией. Проблема здесь в том, что нет ничего подобного параметру пользовательских данных. Существует только один параметр, то есть число сигналов. Поскольку имеется только 64 сигнала, вы можете создать статический массив std::function< void(int) >
и вызвать каждый в зависимости от номера сигнала. Простой пример:
typedef std::function< void(int) > SignalFunc;
static std::array< SignalFunc, 64 > signalsFunc;
static void cHandler(int nSig)
{
signalsFunc.at(nSig)(nSig);
}
SignalFunc RegisterSystemSignal( int sig, SignalFunc func )
{
if( signal( sig, func ? &cHandler : (sighandler_t)SIG_DFL ) != SIG_ERR )
{
func.swap( signalsFunc.at( sig ) );
return func;
}
throw some_error();
}
Итак, теперь вы можете сделать это:
RegisterSystemSignal(SIGFPE, [&](int nSig)->void{handler(nSig,data);});
Существует также sigaction
больше функций.