C++11 lambda表达式在回调函数中的使用方式

目录
  • 一、lambda表达式在C++异步框架中的应用
  • 二、如何在C-style注册回调函数中使用lambda表达式?

在回调函数中使用lambda表达式的好处,在于可以利用C++的RAII机制来做资源的自动申请释放,避免手动管理出错。

一、lambda表达式在C++异步框架中的应用

1. 一个boost asio的例子

//
// async_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class session
  : public std::enable_shared_from_this<session>
{
public:
  session(tcp::socket socket)
    : socket_(std::move(socket))
  {
  }

  void start()
  {
    do_read();
  }

private:
  void do_read()
  {
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        [this, self](boost::system::error_code ec, std::size_t length)
        {
          if (!ec)
          {
            do_write(length);
          }
        });
  }

  void do_write(std::size_t length)
  {
    auto self(shared_from_this());
    boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
        [this, self](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            do_read();
          }
        });
  }

  tcp::socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_context& io_context, short port)
    : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
  {
    do_accept();
  }

private:
  void do_accept()
  {
    acceptor_.async_accept(
        [this](boost::system::error_code ec, tcp::socket socket)
        {
          if (!ec)
          {
            std::make_shared<session>(std::move(socket))->start();
          }

          do_accept();
        });
  }

  tcp::acceptor acceptor_;
};

int test()
{
  try
  {

    boost::asio::io_context io_context;

    server s(io_context, 8080);

    io_context.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

int main(int argc, char** argv){
  test();
}

其中在async_read_some函数的第二个参数使用了lambda表达式作为参数,并且闭包捕获了self变量。这样做的目的是通过shared_ptr增加this的引用计数。 在server::do_accept函数中存在一句代码:

std::make_shared(std::move(socket))->start();

这里有一个表达式亡值,属于shared_ptr类型。当启动start()方法后,会为该智能指针所管理的对象增加一次引用计数。 所以在离开作用域后,shared_ptr析构不会导致实际的session对象析构。

最终当不再继续注册异步读写回调时(在这里的代码中,当读写出现错误时),即放弃该连接的session时, 智能指针的引用计数降为0,触发session对象的析构。

void do_read()
{
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        [this, self](boost::system::error_code ec, std::size_t length)
        {
          if (!ec)
          {
            do_write(length);
          }
        });
}
void do_accept()
{
    acceptor_.async_accept(
        [this](boost::system::error_code ec, tcp::socket socket)
        {
          if (!ec)
          {
            std::make_shared<session>(std::move(socket))->start();
          }

          do_accept();
        });
}

这样使用lambda表达式在资源管理上带来了传统的函数指针不具备的优势。因为当回调函数被执行时,使用传统写法需要在每个条件分支下都要考虑到资源的释放。

2. C++ http框架cutelyst在异步执行PostgreSQL数据库sql请求的例子

void SingleDatabaseQueryTest::dbp(Context *c)
{
    const int id = (qrand() % 10000) + 1;

    ASync async(c);
    static thread_local auto db = APool::database();
    db.exec(APreparedQueryLiteral(u"SELECT id, randomNumber FROM world WHERE id=$1"),
                           {id}, [c, async] (AResult &result) {
        if (Q_LIKELY(!result.error() && result.size())) {
            auto it = result.begin();
            c->response()->setJsonBody(QByteArray::fromStdString(
                            picojson::value(picojson::object({
                                                {"id", picojson::value(double(it[0].toInt()))},
                                                {"randomNumber", picojson::value(double(it[1].toInt()))}
                                            })).serialize()));
            return;
        }

        c->res()->setStatus(Response::InternalServerError);
    }, c);
}

其中ASync的构造函数作用是断开事件处理链,即当这个dbp函数返回时,对该context不去向浏览器发出http响应。代码大致为:

ASync::ASync(Context *c)
{
    c->detachAsync();
}

析构函数作用是恢复事件处理链,即通知eventloop,可以对该context发送http响应了。大致为:

ASync::~ASync()
{
    c->attachAsync();
}

通过在异步sql执行函数中注册一个lambda表达式,lambda表达式捕获一个外部变量,利用RAII机制,能够实现在异步sql执行完毕后再进行http响应。这是lambda表达式闭包捕获变量的优势。

二、如何在C-style注册回调函数中使用lambda表达式?

有一个c库,其中存在一个注册回调函数:

void register_callback(void(*callback)(void *), void * context);

希望可以注册C++11的lambda表达式,而且是带捕获变量的lambda表达式,因为要用捕获变量来维持状态。

首先分析一下这个注册函数:

这个注册回调函数第一个参数是C-style函数指针,不带状态。第二个参数void *context ,携带函数执行的状态。

这样每次函数执行的时候,会将context传递进来,做到了持续保持对状态的访问和修改。

void register_callback( void(*callback)(void*), void * p ) {
  //这里是一个简单的模拟。实际中可能会多次调用callback函数。
  callback(p); //  测试
  callback(p);
}

对于将lambda表达式与函数指针之间的转换,如果没有捕获变量可以直接转换。

void raw_function_pointer_test() {
  int x = 0;
  auto f = [](void* context)->void {
      int *x = static_cast<int*>(context);
      ++(*x);
  };
  register_callback(f, &x);
  std::cout << x << "\n";
}

调用代码

raw_function_pointer_test();

输出:2

但是这种转换方式,完全属于C风格,充满了类型不安全。如果想要使用lambda表达式来直接捕获变量x,则不行。下面这个代码无法通过编译。

void raw_function_pointer_capture_test() {
  int x = 0;
  auto f = [x](void* context) mutable ->void {
      ++x;
  };
  register_callback(f, nullptr);
  std::cout << x << "\n";
}

那有什么方法能够将捕获变量的lambda表达式转换成普通函数指针,同时能够保留状态呢?

方法一: 声明一个全局的invoke_function函数,将lambda表达式转为为void*,即将lambda表达式作为状态传递。

extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}
void lambda_to_function(){
    int x = 0;
    auto lambda_f = [&]()->void { ++x; };
    std::function cpp_function(std::move(lambda_f));
    register_callback(&invoke_function, &cpp_function);
    std::cout << x << "\n";
}

调用代码

lambda_to_function();

输出:2

std::function cpp_function用于接管lambda表达式的所有权,状态都存在这里。此处使用的是栈变量,可以根据实际的需要变成堆变量,防止cpp_function析构后再使用,成为undefined behavior。

方法二:使用模板,将状态存在一个结构体里面。

#include <iostream>
#include <tuple>
#include <memory>

template<class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  std::unique_ptr<void, void(*)(void*)> state;
};
template<typename... Args, typename Lambda>
callback<Args...> voidify( Lambda&& l ) {
  using Func = typename std::decay<Lambda>::type;
  std::unique_ptr<void, void(*)(void*)> data(
    new Func(std::forward<Lambda>(l)),
    +[](void* ptr){ delete (Func*)ptr; }
  );
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::move(data)
  };
}

void lambda_capture_template_test() {
  int x = 0;
  auto closure = [&]()->void { ++x; };
  auto voidified = voidify(closure);
  register_callback( voidified.function, voidified.state.get() );
//   register_callback( voidified.function, voidified.state.get() );
  std::cout << x << "\n";
}

调用代码

lambda_capture_template_test();

输出:2

稍微解释一下模板做法的含义。

template<class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  std::unique_ptr<void, void(*)(void*)> state;
};

这个模板类callback,第一个成员就是普通函数指针,用于注册回调函数时使用。第二个成员是自定义deleter的unique_ptr,智能指针管理的是一个匿名类(即lambda表达式所属的类)。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • C++11中lambda、std::function和std:bind详解

    前言 在C++11新标准中,语言本身和标准库都增加了很多新内容,本文只涉及了一些皮毛.不过我相信这些新特性当中有一些,应该成为所有C++开发者的常规装备.本文主要介绍了C++11中lambda.std::function和std:bind,下面来一起看看详细的介绍吧. lambda 表达式 C++11中新增了lambda 表达式这一语言特性.lambda表达式可以让我们快速和便捷的创建一个"函数". 下面是lambda表达式的语法: [ capture-list ] { body }

  • C++中的Lambda函数详解

    目录 一 函数语法 二 函数应用 1.在普通函数中使用 2.在qt信号槽中使用 3.在std::sort排序函数中的使用 三 总结 一 函数语法 我们平时调用函数的时候,都是需要被调用函数的函数名,但是匿名函数就不需要函数名,而且直接写在需要调用的地方,对于以前没用过的小伙伴来说,第一眼看见了这语法可能很迷惑. C++11的基本语法格式为: [capture](parameters) -> return_type { /* ... */ } (1) [capture] :[]内为外部变量的传递方

  • 一文读懂c++11 Lambda表达式

    1.简介 1.1定义 C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Java ,C#等.本质上, Lambda 表达式是一个可调用的代码单元[1]^{[1]}[1].实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数.代理等功能.Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了C

  • C++11 lambda表达式在回调函数中的使用方式

    目录 一.lambda表达式在C++异步框架中的应用 二.如何在C-style注册回调函数中使用lambda表达式? 在回调函数中使用lambda表达式的好处,在于可以利用C++的RAII机制来做资源的自动申请释放,避免手动管理出错. 一.lambda表达式在C++异步框架中的应用 1. 一个boost asio的例子 // // async_tcp_echo_server.cpp // ~~~~~~~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-202

  • python lambda表达式在sort函数中的使用详解

    1.lambda表达式一般用法 语法: lamda argument:expression example: add = lambda x, y: x+y print(add(10, 20))<br data-filtered="filtered">>>> 30 2.lambda表达式在sort函数中的使用 假如a是一个由元组构成的列表,对该列表进行排序时,我们需要用到参数key,也就是关键词,如下面代码所示,lambda是一个匿名函数,是固定写法:x表示

  • 深入解析C++11 lambda表达式/包装器/线程库

    目录 零.前言 一.lambda表达式 1.lambda的引入 2.lambda表达式语法 3.捕获列表说明 4.函数对象与lambda表达式 二.包装器 1.function包装器 2.bind 概念: 三.线程库 1.线程的概念及使用 2.线程函数参数 3.原子性操作库(atomic) 4.lock_guard与unique_lock 1.mutex的种类 2.lock_guard 3.unique_lock 5.两个线程交替打印奇数偶数 零.前言 本章是讲解学习C++11语法新特性的第三篇

  • 关于javascript 回调函数中变量作用域的讨论

    1.背景 Javascript中的回调函数,相信大家都不陌生,最明显的例子是做Ajax请求时,提供的回调函数, 实际上DOM节点的事件处理方法(onclick,ondblclick等)也是回调函数. 在使用DWR的时候,回调函数可以作为第一个或者最后一个参数出现,如: JScript code function callBack(result){ } myDwrService.doSomething(param1,param2,callBack);//DWR的推荐方式 //或者 myDwrSer

  • python lambda表达式(匿名函数)写法解析

    这篇文章主要介绍了python lambda表达式(匿名函数)写法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 # lambda表达式,为了解决简单函数的情况,如: def func(a1,a2): return a1 + a2 func = lambda a1,a2:a1+a2 # 上面这两个是一样的 def func1(a1,a2): return a1 + a2 func2 = lambda a1,a2:a1+a2 wdc = fu

  • ajax异步回调函数中给外部变量赋值的问题探讨

    复制代码 代码如下: <!doctype html> <head> <meta charset="utf-8"> </head> <body> <script src="http://localhost/UIBMSPHPProj/public/js/jquery-1.8.2.min.js"></script> <script> $(function(){ var str

  • python回调函数中使用多线程的方法

    下面的demo是根据需求写的简单测试脚本 #!/usr/bin/env python # coding: utf-8 # 第一个列表为依赖组件和版本号,后面紧跟负责人名称 # 接着出现第二个以来组建列表,负责人为空了 # 所以根据需求需要对组件.版本号.负责人进行不同处理 # 这时在for循环中根据if判断,写回调函数处理 # 格式不一致数据的测试数据 a = [[u'tool-1', u'1.9.13'], u'xiaowang', u'xiaoqu', [u'tool-2', u'1.9.2

  • python在回调函数中获取返回值的方法

    python中有用到回调函数的时候,而回调函数又需要返回数值的时候,就需要先将所被传为回调函数的函数先赋值给一个变量,然后等回调结束之后,将这个变量取值回来就可以了. 如我用到到的调用xmlreader时,传入的一个函数需要取回返回值的代码: # 创建一个 XMLReader parser = xml.sax.make_parser() # turn off namepsaces parser.setFeature(xml.sax.handler.feature_namespaces, 0) #

  • 浅谈vue使用axios的回调函数中this不指向vue实例,为undefined

    今天在vue-cli脚手架搭建的项目中使用axios时,遇到无法解析this.$route的报错信息,最后发现是作用域的问题. 1.解决方法:使用 => 原代码: axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); 修改为: axios.get('/u

随机推荐