我正在尝试使用signal(int,void(*)(int))
中的函数<csignal>
来处理浮点异常SIGFPE。我希望能够打印一些有用的诊断,除了只是一条消息说“浮点异常”或其他类似的东西。这意味着我作为signal
的处理程序传递的函数需要访问我的代码中的一些数据。这就是问题所在。
该函数必须返回void
并且只接受int
类型的1个参数。我不能使处理程序成为我的数据存储类的成员函数,因为由于隐藏的void(Foo::*)(int)
指针,类型将是this
。
我想过使用lambdas尝试创建这样的匿名函数;
void handler(int nSig, Foo data)
{
// do something
}
// snip
Foo data;
signal(SIGFPE, [&](int nSig)->void{handler(nSig,data);});
但是因为lambda从外部捕获变量data
,所以编译器不会将它转换为指向void(*)(int)
的指针(这是一种耻辱,因为这似乎是lambdas的理想用法)。
我可以简单地将data
作为一个全局变量,然后可以在handler
中看到,但由于显而易见的原因,我不愿意这样做。
所以我的问题是这样的; 在C ++中模仿匿名函数的最佳方法是什么??
注意:我更喜欢本机C ++解决方案而不必使用boost或等效。
答案 0 :(得分:13)
这确实是个好问题。让我们弄清楚在归咎于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;
}
到目前为止一切顺利。但是现在你想要将一些数据与你的信号处理程序相关联(顺便说一句,上面的代码是未定义的行为,如you can only execute signal-safe code inside 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 ++ 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;
}
上面是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;
}
在上面的两个例子中,请注意“数据”总是传递。这是非常重要的。如果应该调用您的回调的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
安装信号处理程序,将其与任意上下文关联,并在回调函数中执行任何操作。
无论如何,让我们回到你正在玩的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;
}
因此,C ++在这里所做的事情并不羞耻,因为它做得对。
答案 1 :(得分:8)
在C中没有匿名函数(C ++在这里是无关紧要的,因为函数必须遵守C调用约定)。
你唯一能做的就是从处理程序中获取 shiver 访问全局变量,可能是全局变量(而不是那些没有常数的常量)。
我建议制作那些全局线程本地以避免多线程问题,但在全局变量使更脆弱的应用程序出现的意义上它仍然是错误。
如何?
注意:正如Luc Danton耐心地向我解释的那样,信号可能会中断任何非原子活动,因此只有当它是无锁原子(或其他一些东西)时,从全局读取才是安全的。 。 不幸的是std::function
可能不是这样,具体取决于您的实施,我仍然会留下此代码来解释如何完成提供std::function
访问是原子的< /强> 的
可以创建一个蹦床,它将调用有状态的东西,隔离线程并允许重入调用。
typedef std::function<void(int)> SignalHandlerType;
extern thread_local ignalHandlerType SignalHandler;
我们创建了以下访问器(传递给信号):
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
不是...... < / em>的
预期用途:
int main(int argc, char* argv[]) {
SignalSetter setter(SIGFPE, [argc, argv]() {
std::cout << argc << ": " << argc << std::endl;
});
// do what you want.
}
答案 2 :(得分:0)
您无法在运行时轻松创建新的静态函数,某些JIT编译器库可以执行此操作。 如果只需要合理数量的指针,则可以通过专门化模板来创建一些静态函数池。
最简单的方法是用静态函数包装C ++ Functors。这里的问题是没有像用户数据参数那样的东西。只有一个参数,即一些信号。由于只有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
女巫有更多功能。