我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

在回调函数中使用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 )
        {
          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表达式所属的类)。

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

这篇文章主要介绍了C++11lambda表达式在回调函数中的使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-11-13

C++ 函数调用 Lambda 表达式:参数传递和返回值的回调优化

在 c++++ 中,可以使用 lambda 表达式作为函数参数,实现回调函数的灵活性。具体而言:参数传递:通过 std::function 包装 lambda 表达式,以函数指针形式传递给函数。返回值处理:使用 std::function
C++ 函数调用 Lambda 表达式:参数传递和返回值的回调优化
2024-05-03

C++ 函数指针与 lambda 表达式:揭晓回调魔法

函数指针和 lambda 表达式均允许将函数作为参数传递给回调函数。函数指针保存指向函数地址的变量,而 lambda 表达式是匿名函数对象,可即时定义函数。通过实战案例,我们演示了使用函数指针和 lambda 表达式对数组元素求和。这些技术
C++ 函数指针与 lambda 表达式:揭晓回调魔法
2024-04-29

如何使用C++中的正则表达式函数?

如何使用C++中的正则表达式函数?正则表达式是一种强大的文本处理工具,可以用于匹配、搜索和替换文本中的模式。在C++中,我们可以使用正则表达式函数库来实现对文本的处理。本文将介绍如何在C++中使用正则表达式函数。首先,我们需要包含C++标准
如何使用C++中的正则表达式函数?
2023-11-18

c++中函数的调用方式

c++ 中有两种函数调用方式:值调用和引用调用。值调用传递参数副本,不影响原始变量;引用调用传递参数引用,修改引用会影响原始变量。根据函数目的和效率考虑,选择适当的调用方式:值调用保护原始变量,引用调用修改原始变量。C++ 中函数的调用方式
c++中函数的调用方式
2024-05-06

C++11 成员函数作为回调函数的使用方式

这篇文章主要介绍了C++11 成员函数作为回调函数的使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-11-13

C++ 函数中 lambda 表达式的使用案例有哪些?

c++++函数中的lambda表达式用例:回调函数:传递给其他函数或对象作为回调函数。仿函数:提供自定义比较器或谓词。事件处理:响应事件的回调函数。代码简化:消除对命名函数的需要。匿名函数:定义不需要命名的情况下使用。C++ 函数中 lam
C++ 函数中 lambda 表达式的使用案例有哪些?
2024-04-25

PHP 正则表达式函数的使用方法

php 正则表达式函数提供强大的文本处理能力,包括:preg_match:检查字符串中是否存在匹配模式。preg_match_all:获取字符串中所有匹配模式的数组。preg_replace:用替换文本替换字符串中的所有匹配模式。preg_
PHP 正则表达式函数的使用方法
2024-04-21

C++ 函数参数传递方式与 lambda 表达式的关系

函数参数传递方式决定了参数在调用者和函数实现之间传递的方式,包括值传递、引用传递和 const 引用传递。lambda 表达式通过值捕获来访问函数外部变量,捕获类型(值捕获、引用捕获或无捕获)影响着 lambda 表达式的执行效率和可修改性
C++ 函数参数传递方式与 lambda 表达式的关系
2024-04-12

lambda表达式在java8中的使用方法

这篇文章给大家介绍lambda表达式在java8中的使用方法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。定义 TantanitReader:public class TantanitReader { private
2023-05-31

C#中的匿名函数、lambda表达式解读

这篇文章主要介绍了C#中的匿名函数、lambda表达式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-01-28

C++中有哪些函数调用的方式

这篇文章将为大家详细讲解有关C++中有哪些函数调用的方式,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学
2023-06-17

解析Python中回调函数的原理及使用方式

Python回调函数的原理和用法解析回调函数是一种常见的编程技术,尤其在Python中被广泛使用。它可以使我们在异步编程中更加灵活地处理事件和执行任务。本文将对回调函数的原理和用法进行详细解析,并提供具体的代码示例。一、回调函数的原理回
解析Python中回调函数的原理及使用方式
2024-02-02

C++ 函数声明中的 lambda 表达式:探索匿名函数的灵活使用

lambda 表达式是一种匿名函数,可在函数声明中创建并传递函数对象,提高代码灵活性和可读性。其语法为:[捕获列表] (参数列表) -> 返回类型 { 函数体 }。在实际应用中,它提供了比函数指针更简洁、灵活的方法,例如创建匿名函数 lam
C++ 函数声明中的 lambda 表达式:探索匿名函数的灵活使用
2024-05-03

C++ 函数默认参数和可变参数在 Lambda 表达式中的应用

lambda 表达式中,默认参数允许指定参数默认值,而可变参数则允许传递数量不定的参数。默认参数应紧随必选参数,而可变参数必须是函数参数中最后一个。这些功能可以简化代码并提高可读性,例如在处理字符串列表时添加前缀和后缀。C++ 函数默认参数
C++ 函数默认参数和可变参数在 Lambda 表达式中的应用
2024-04-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录