Dan*_*Dan 15 c++ lambda anonymous-function c++11
我想使用的功能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);});
Run Code Online (Sandbox Code Playgroud)
但是因为lambda data
从外部捕获变量,所以编译器不会将它强制转换为指针void(*)(int)
(这是一种耻辱,因为这似乎是lambdas的理想用法).
我可以简单地创建data
一个可以在其中看到的全局变量,handler
但出于显而易见的原因我不愿意这样做.
所以我的问题是这样的; 在C++中模仿匿名函数的最佳方法是什么?
注意:我更喜欢本机C++解决方案而不必使用boost或等效.
小智 13
这确实是个好问题.让我们弄清楚在归咎于C++之前会发生什么.试想一下lambdas是如何实现的.
最简单的lambda是没有捕获数据的时候.如果是这种情况,其底层类型将成为一个简单的普通函数.例如,像这样的lambda:
[] (int p0) {}
Run Code Online (Sandbox Code Playgroud)
将是一个简单函数的等价物:
void foo(int p0)
{
}
Run Code Online (Sandbox Code Playgroud)
这实际上非常有效,以防您希望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;
}
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.但是现在你想要将一些数据与你的信号处理程序相关联(顺便说一下,上面的代码是未定义的行为,因为你只能在信号处理程序中执行信号安全代码).所以你想要一个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;
}
Run Code Online (Sandbox Code Playgroud)
让我们暂时忘掉一下C++ lambdas的语法糖.即使在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;
}
Run Code Online (Sandbox Code Playgroud)
上面是C风格,C++倾向于将"context"作为"this"传递,这总是隐含的第一个参数.如果我们的API支持将"data"作为第一个参数传递,我们可以将指针应用于成员转换(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;
}
Run Code Online (Sandbox Code Playgroud)
在上面两个例子中,请注意"数据"总是传递.这是非常重要的.如果应该调用您的回调的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;
}
Run Code Online (Sandbox Code Playgroud)
话虽这么说,一个旧的信号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;
}
Run Code Online (Sandbox Code Playgroud)
这是人们必须处理的问题.不仅在信号API的情况下.这也适用于其他事情.例如,中断处理程序处理.但是你需要处理硬件的那种低级编程.当然,在用户空间中提供这种API并不是最好的主意.我会再次提到它 - 在信号处理程序中只能做一小部分事情.您只能调用异步信号安全功能.
当然,旧的API不会很快消失,因为它实际上是POSIX标准.但是,开发人员认识到这个问题并且有更好的方法来处理信号.例如,在Linux中,您可以使用eventfd
安装信号处理程序,将其与任意上下文关联,并在回调函数中执行任何操作.
无论如何,让我们回到你正在玩的lambda.问题不在于C++,而是使用信号API,除了使用全局变量之外,您无法传递上下文.话虽这么说,它也适用于lambdas:
#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;
}
Run Code Online (Sandbox Code Playgroud)
因此,C++在这里所做的事情并不羞耻,因为它做得对.
在C中没有匿名函数(C++在这里是无关紧要的,因为函数必须遵守C调用约定).
你唯一能做的就是从处理程序中获取shiver访问全局变量,可能是全局变量(而不是常量,这很好).
我建议将这些全局变量线程局部化以避免多线程问题,但是在全局变量导致更脆弱的应用程序的意义上它仍然很糟糕.
如何 ?
注意:正如Luc Danton耐心地向我解释的那样,信号可能会中断任何非原子活动,因此只有当它是无锁原子(或其他一些东西)时,从全局读取才是安全的.不幸的是std::function
可能并非如此,这取决于您的实现,我依然会离开这个代码解释它如何能做到提供std::function
的访问是原子.
可以创建一个蹦床,它将调用有状态的东西,隔离线程并允许重入调用.
typedef std::function<void(int)> SignalHandlerType;
extern thread_local ignalHandlerType SignalHandler;
Run Code Online (Sandbox Code Playgroud)
我们创建了以下访问器(传递给signal):
void handle_signal(int const i) {
if (SignalHandler) { SignalHandler(i); }
}
Run Code Online (Sandbox Code Playgroud)
以及以下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;
};
Run Code Online (Sandbox Code Playgroud)
注意:无论是全局变量和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.
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
8176 次 |
最近记录: |