Как написать анонимную функцию / лямбду, которая передает себя в качестве обратного вызова? - PullRequest
7 голосов
/ 14 декабря 2011

Я учусь boost :: asio и C ++ 11 одновременно.Одна из моих тестовых программ, которая на самом деле является адаптацией одного из примеров, приведенных в учебнике boost :: asio , выглядит следующим образом:

#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 конструктора выходит из области видимости, а переменная callback лямбда-выражения, которая является ссылкой на переменную callback конструктора, становится висячей ссылкой.

ИтакЯ изменяю критическую строку следующим образом:

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

Затем скомпилирую ее, запустлю и получу ошибку вызова функции.Опять же, я понимаю, почему я получаю ошибку вызова функции.В области видимости лямбды переменной callback конструктора до сих пор не было присвоено никакого значения, поэтому она для всех практических целей представляет собой висячий указатель на функцию.Следовательно, переменная callback лямбды, которая является копией переменной callback конструктора, также является указателем на висячую функцию.


Подумав некоторое время над этой проблемой, я понял, чтоМне действительно нужно, чтобы обратный вызов мог ссылаться на себя , используя указатель на функцию, а не ссылку на указатель на функцию .Пример достиг этого, используя именованную функцию в качестве обратного вызова вместо анонимной.Однако передача именованных функций в качестве обратных вызовов не очень элегантна.Есть ли способ получить анонимную функцию с указателем на себя в качестве локальной переменной?

Ответы [ 5 ]

8 голосов
/ 14 декабря 2011

Есть несколько альтернатив:

  • Хватит использовать лямбду. Вы не должны использовать их для всего , вы знаете. Они охватывают множество случаев, но не все. Просто используйте обычный старый функтор.
  • Пусть лямбда хранит умный указатель на динамически распределяемый 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;
    
3 голосов
/ 15 декабря 2011

Чтобы узнать об Asio и C ++ 11, я рекомендую выступление boostcon «Почему C ++ 0x - самый удивительный язык для сетевого программирования» от самого дизайнера asio. (Кристофер Колхофф)

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 одну за другой. В середине разговора есть часть о лямбде. Тип проблемы, с которой вы сталкиваетесь при использовании лямбды, заключается в следующем:

#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 голосов
/ 23 февраля 2016

В настоящее время есть предложение добавить Y-комбинатор в стандартную библиотеку C ++ (P0200R0) , чтобы решить именно эту проблему.

Основная идея здесь заключается в том, что вы передаете лямбду себе в качестве первого аргумента. Невидимый вспомогательный класс заботится о рекурсивном вызове в фоновом режиме, сохраняя лямбду в именованном члене.

Пример реализации из предложения выглядит следующим образом:

#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);
    }
}));

Обратите внимание на аргумент self, который передается в лямбду. Это будет связано с результатом вызова y_combinator, который является функциональным объектом, эквивалентным лямбда-выражению с уже связанным аргументом self (т.е. его подпись void(const boost::system::error_code&)).

1 голос
/ 15 декабря 2011

Только теория: вы можете делать такие вещи с помощью так называемых комбинаторов (таких как I, S, K).

Перед использованием анонимного лямбда-выражения e типа F вы можете сначала определить функции, такие как doubleF ; (F) -> (F, F) или applyToOneself : (F f) -> F = {return f (f); }.

1 голос
/ 14 декабря 2011

Лямбда захватывает путем ссылки на локальную переменную «обратный вызов», когда запускается лямбда, которая не будет действительной.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...