如何编写一个将自身作为回调传递的匿名函数/ lambda?

时间:2011-12-14 05:27:10

标签: c++ c++11

我正在同时学习boost :: asio和C ++ 11。我的一个测试程序实际上是one of the samples given in the boost::asio tutorial的改编版,如下所示:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

class printer {

// Static data members
private:
    const static boost::posix_time::seconds one_second;

// Instance data members
private:
    boost::asio::deadline_timer timer;
    int count;

// Public members
public:
    printer(boost::asio::io_service& io)
        : timer(io, one_second), count(0) {

        std::function<void(const boost::system::error_code&)> callback;
        callback = [&](const boost::system::error_code&) { // critical line
            if (count < 5) {
                std::cout << "Current count is " << count++ << std::endl;

                timer.expires_at(timer.expires_at() + one_second);
                timer.async_wait(callback);
            }
        };

        timer.async_wait(callback);
    }

    ~printer() {
        std::cout << "Final count is " << count << std::endl;
    }
};

const boost::posix_time::seconds printer::one_second(1);

int main() {
    boost::asio::io_service io;
    printer p(io);
    io.run();

    return 0;
}

当我运行此程序时,我遇到了分段错误。我明白为什么会出现分段错误。构造函数完成运行后,构造函数的callback变量超出范围,lambda的callback变量(它是对构造函数的callback变量的引用)变为悬空引用。 / p>

所以我用以下方法修改关键线:

        callback = [callback, &](const boost::system::error_code&) { // critical line

然后编译它,运行它,并得到一个错误的函数调用错误。再次,我明白为什么我得到了错误的函数调用错误。在lambda的范围内,构造函数的callback变量仍然没有被赋值给任何值,所以它实际上是一个悬空函数指针。因此,lambda的callback变量是构造函数的callback变量的副本,也是一个悬空函数指针。


在考虑了这个问题一段时间后,我意识到我真正需要的是回调能够使用函数指针引用自身,而不是对函数指针的引用。该示例通过使用命名函数作为回调而不是匿名函数来实现此目的。但是,将命名函数作为回调传递并不是很优雅。有没有办法让匿名函数有一个函数指针将自己作为局部变量?

5 个答案:

答案 0 :(得分:8)

有几种选择:

  • 停止使用lambda。你知道,你不必将它们用于一切。它们涵盖了很多案例,但它们并不涵盖所有内容。只需使用常规的旧仿函数。
  • 让lambda存储一个智能指针,指向存储lambda的动态分配的std::function。例如:

    auto pCallback = std::make_shared<std::function<void(const boost::system::error_code&)>>();
    auto callback = [=](const boost::system::error_code&) { // critical line
        if (count < 5) {
            std::cout << "Current count is " << count++ << std::endl;
    
            timer.expires_at(timer.expires_at() + one_second);
            timer.async_wait(pCallback.get());
        }
    };
    *pCallback = callback;
    

答案 1 :(得分:3)

要了解Asio和C ++ 11,我建议由asio本人的设计者使用boostcon“为什么C ++ 0x是网络编程最棒的语言”。 (Christopher Kohlhoff)

https://blip.tv/boostcon/why-c-0x-is-the-awesomest-language-for-network-programming-5368225 http://github.com/chriskohlhoff/awesome

在本次演讲中,C.K采用典型的小型asio应用程序,并开始逐个添加C ++ 11功能。谈话的中间部分有关于lambda的部分内容。您使用lambda的生命周期的问题是使用以下模式解决方法:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <memory>

class printer 
{

// Static data members
private:
    const static boost::posix_time::seconds one_second;

// Instance data members
private:
    boost::asio::deadline_timer timer;
    int count;

// Public members
public:
    printer(boost::asio::io_service& io)
        : timer(io, one_second), count(0) {
       wait();
    }

    void wait() {
        timer.async_wait(
            [&](const boost::system::error_code& ec) {
               if (!ec && count < 5) {
                 std::cout << "Current count is " << count++ << std::endl;

                 timer.expires_at(timer.expires_at() + one_second);
                 wait();
               }
            });
    }

    ~printer() {
        std::cout << "Final count is " << count << std::endl;
    }
};

const boost::posix_time::seconds printer::one_second(1);

int main() {
    boost::asio::io_service io;
    printer p(io);
    io.run();

    return 0;
}

答案 2 :(得分:2)

现在有人向add a Y-combinator to the C++ standard library (P0200R0)提出解决此问题的建议。

这里的基本思想是将lambda作为第一个参数传递给自己。一个不可见的助手类通过将lambda存储在一个命名成员中来处理后台的递归调用。

提案中的示例实现如下所示:

#include <functional>
#include <utility>

namespace std {

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

} // namespace std

可以使用以下方法解决问题:

timer.async_wait(std::y_combinator([](auto self, const boost::system::error_code&) {
    if (count < 5) {
        std::cout << "Current count is " << count++ << std::endl;

        timer.expires_at(timer.expires_at() + one_second);
        timer.async_wait(self);
    }
}));

注意传递给lambda的self参数。 这将绑定到y_combinator调用的结果,该调用是一个函数对象,它等同于已绑定self参数的lambda(即,其签名为void(const boost::system::error_code&))。

答案 3 :(得分:1)

当lambda运行无效时,lambda将通过引用局部变量“callback”来捕获。

答案 4 :(得分:1)

只有理论:你可以用所谓的组合器(比如I,S,K)来做这些事情。

在使用F类型的匿名lambda表达式e之前,您可以先定义 doubleF 等函数; (F) - &gt; (F,F)或 applyToOneself :( F f) - &gt; F = {return f(f); }。