Это действительно хороший вопрос. Давайте выясним, что происходит, прежде чем обвинять C++. Просто подумайте о том, как внедрены лямбды.
Самая простая лямбда, когда данные не фиксируются. Если это так, его базовый тип становится простой простой функцией. Например, лямбда, как это:
[] (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;
}
Пока все хорошо. Но теперь вы хотите связать некоторые данные с вашим обработчиком сигнала (кстати, код выше является неопределенным поведением как you can only execute signal-safe code inside a signal handler). Таким образом, вы хотите, лямбда, как:
#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;
}
Давайте забудем на минуту о синтаксическом сахаре лямбды C++. Не секрет, что вы можете «имитировать» лямбда даже на C или ассемблере. Так как же это будет выглядеть? «Лямбда» в C-стиле может выглядеть следующим образом (это все 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, C++, как правило, проходит «контекст», как «это», которое всегда неявно первый аргумент , Если наш API поддерживается прохождение «данных» в качестве первого аргумента, мы могли бы применить указатель преобразование членов (ИМП) и написать что-то вроде этого:
#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-интерфейса в пользовательском пространстве было не лучшей идеей. И я расскажу об этом еще раз - в обработчике сигналов есть только небольшой набор вещей. Вы можете позвонить только async-signal-safe functions.
Конечно, старый API не уходит в ближайшее время, потому что это фактически стандарт POSIX. Тем не менее, разработчики распознают проблему, и есть более эффективные способы обработки сигналов. Например, в Linux вы можете использовать eventfd
для установки обработчика сигнала, связать его с произвольным контекстом и делать все, что хотите, в функции обратного вызова.
Во всяком случае, давайте вернемся к лямбде, с которой вы играли. Проблема не в C++, но с сигналами 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;
}
Таким образом, нет ничего постыдного в том, что C++ делает здесь, как это делает правильно.
Что значит вы не можете использовать 'data' в' void (*) (int) '? 'data' - это экземпляр' Foo' ... Почему вы не можете называть 'data.handler (nSig)'? – David
Если я делаю 'handler' членом класса' Foo', то это сигнатура функции теперь 'void (Foo :: *) (int)' not 'void (*) (int)' причина в том, что нестатический член функции имеют скрытый указатель на вызывающий объект ('this' является скрытым первым аргументом) :-( – Dan
@Dave Он имел в виду, что он не может наложить * лямбда *. –