这确实是一个好问题。在指责 C++ 之前,让我们先弄清楚到底发生了什么。想想 lambda 是如何实现的。
最简单的 lambda 是在没有捕获数据时。如果是这样的话,它的底层类型就变成了一个简单的普通函数。例如,像这样的 lambda:
[] (int p0) {}
将相当于一个简单的函数:
void foo(int p0)
{
}
如果您希望 lambda 成为函数指针,这实际上非常有效。例如:
#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;
}
到目前为止,一切都很好。但现在您想要将一些数据与信号处理程序相关联(顺便说一句,上面的代码是未定义的行为,如您只能在信号处理程序中执行信号安全代码 http://lazarenko.me/2013/01/15/how-not-to-write-a-signal-handler/)。所以你想要一个像这样的 lambda:
#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++ lambda 的语法糖。即使在 C 或汇编程序中,您也可以“模仿”lambda,这已不是什么秘密。那么实际上看起来怎么样? 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 风格,C++ 倾向于将“context”作为“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 并不是最好的主意。我会再次提到 - 在信号处理程序中您只能做一小部分事情。你只能打电话异步信号安全函数 https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers.
当然,旧的 API 不会很快消失,因为它实际上是 POSIX 标准。然而,开发人员认识到了这个问题,并且有更好的方法来处理信号。例如,在 Linux 中,您可以使用eventfd http://www.kernel.org/doc/man-pages/online/pages/man2/eventfd.2.html要安装信号处理程序,请将其与任意上下文关联并在回调函数中执行您想要的操作。
无论如何,让我们回到您正在使用的 lambda。问题不在于 C++,而在于信号 API,除了使用全局变量之外,您无法传递上下文。话虽这么说,它也适用于 lambda:
#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++ 在这里所做的事情并不可耻,因为它做了正确的事情。