匿名函数C ++

时间:2012-05-30 12:16:44

标签: c++ lambda c++11 anonymous-function

我正在尝试使用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或等效。

3 个答案:

答案 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都可以privateSignalSetter类......但由于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女巫有更多功能。