匿名函数C++

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++在这里所做的事情并不羞耻,因为它做得对.

  • 所以这个答案结束了:) (4认同)
  • 请注意,使用(无状态)lambda没有正式定义良好,因为转换产生指向未指定链接的函数的指针,而我们需要C链接.我也指导你[我的其他评论](http://stackoverflow.com/questions/10816107/anonymous-function-c#comment14079603_10816266). (2认同)

Mat*_* M. 8

在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可能privateSignalSetter类...但由于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)

  • 我不会说C++是无关紧要的,因为如果C++从捕获到函数指针提供了一个垫片,这将是有效的.请注意,虽然C++没有*提供这样的方式(出于技术原因)但实际上并非不可能. (4认同)