当前位置:主页 > c/c++教程 > C++11 lambda表达式在回调函数的使用

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

发布:2023-03-08 14:00:01 59


本站收集了一篇相关的编程文章,网友浦琬玲根据主题投稿了本篇教程内容,涉及到C++11、lambda表达式、使用回调函数、C++11、lambda、C++11 lambda表达式在回调函数的使用相关内容,已被867网友关注,下面的电子资料对本篇知识点有更加详尽的解释。

C++11 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 
#include 
#include 
#include 
#include 

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

class session
  : public std::enable_shared_from_this
{
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(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(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(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*>(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 
#include 
#include 

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

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

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


相关文章

  • C++11中异常处理机制详解

    发布:2023-03-02

    传统的C语言处理异常的方式有两种:终止程序和返回错误码。在实际中的C语言程序基本都是通过返回错误码的方式来处理错误的,部分情况下使用终止程序来处理比较严重的错误。本文将通过示例和大家聊聊C++11中异常处理机制,需要的可以参考一下


  • Python使用lambda表达式对字典排序的实例代码分享

    发布:2020-01-30

    这篇文章主要介绍了Python使用lambda表达式对字典排序操作,结合实例形式分析了lambda表达式实现字典按键排序、按值排序、多条件排序相关操作技巧,需要的朋友可以参考下


  • Python的条件表达式和lambda表达式实例

    发布:2022-07-11

    给网友朋友们带来一篇关于Python的教程,今天小编就为大家分享一篇Python的条件表达式和lambda表达式实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧


  • C++可变参数模板深入深剖

    发布:2023-03-05

    个可变参数模板(variadic template)就是一个接受可变数目参数的函数模板或类模板,下面这篇文章主要给大家介绍了关于C++可变参数模板的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下


  • Java Lambda表达式详解

    发布:2023-03-30

    这篇文章主要介绍了Java Lambda表达式详解,包括了Java Lambda表达式创建线程,Java Lambda表达式的语法,Java lambda遍历List集合,Java lambda过滤String需要的朋友可以参考下


  • Python3中lambda表达式与函数式编程讲解

    发布:2022-06-15

    给大家整理了关于Python3的教程,今天小编就为大家分享一篇关于Python3中lambda表达式与函数式编程讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧


  • C++11正则表达式详解(regex_match、regex_search和regex_replace)

    发布:2023-03-02

    正则表达式(regular expression)是计算机科学中的一个概念,又称规则表达式,下面这篇文章主要介绍了C++11正则表达式(regex_match、regex_search和regex_replace)的相关资料,需要的朋友可以参考下


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

    发布:2023-03-10

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


网友讨论