欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++11 lambda表達式在回調函數中的使用方式

 更新時間:2022年11月05日 10:13:51   作者:MC-Zhang  
這篇文章主要介紹了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 <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對象析構。

最終當不再繼續(xù)注冊異步讀寫回調時(在這里的代碼中,當讀寫出現錯誤時),即放棄該連接的session時, 智能指針的引用計數降為0,觸發(fā)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表達式在資源管理上帶來了傳統的函數指針不具備的優(yōu)勢。因為當回調函數被執(zhí)行時,使用傳統寫法需要在每個條件分支下都要考慮到資源的釋放。

2. C++ http框架cutelyst在異步執(zhí)行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不去向瀏覽器發(fā)出http響應。代碼大致為:

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

析構函數作用是恢復事件處理鏈,即通知eventloop,可以對該context發(fā)送http響應了。大致為:

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

通過在異步sql執(zhí)行函數中注冊一個lambda表達式,lambda表達式捕獲一個外部變量,利用RAII機制,能夠實現在異步sql執(zhí)行完畢后再進行http響應。這是lambda表達式閉包捕獲變量的優(yōu)勢。

二、如何在C-style注冊回調函數中使用lambda表達式?

有一個c庫,其中存在一個注冊回調函數:

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

希望可以注冊C++11的lambda表達式,而且是帶捕獲變量的lambda表達式,因為要用捕獲變量來維持狀態(tài)。

首先分析一下這個注冊函數:

這個注冊回調函數第一個參數是C-style函數指針,不帶狀態(tài)。第二個參數void *context ,攜帶函數執(zhí)行的狀態(tài)。

這樣每次函數執(zhí)行的時候,會將context傳遞進來,做到了持續(xù)保持對狀態(tài)的訪問和修改。

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表達式轉換成普通函數指針,同時能夠保留狀態(tài)呢?

方法一: 聲明一個全局的invoke_function函數,將lambda表達式轉為為void*,即將lambda表達式作為狀態(tài)傳遞。

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表達式的所有權,狀態(tài)都存在這里。此處使用的是棧變量,可以根據實際的需要變成堆變量,防止cpp_function析構后再使用,成為undefined behavior。

方法二:使用模板,將狀態(tài)存在一個結構體里面。

#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表達式所屬的類)。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • MATLAB全網最全的colormap的使用教程詳解

    MATLAB全網最全的colormap的使用教程詳解

    眾所周知,MATLAB中的colormap只有少得可憐的幾種,有很多應用在很特殊的圖形中的colormap幾乎都沒有,而每次寫代碼都要去找顏色的圖屬實太麻煩。所以本文將包全部集成了進來,終于有了這套包含200個colormap的工具函數,希望對大家有所幫助
    2023-02-02
  • C語言關于注釋的知識點總結

    C語言關于注釋的知識點總結

    在本篇文章里小編給大家分享的是關于C語言關于注釋的知識點總結,需要的朋友們可以參考學習下。
    2020-02-02
  • Qt5多線程編程的實現

    Qt5多線程編程的實現

    Qt通過三種形式提供了對線程的支持,本文主要介紹了Qt5多線程編程的實現,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • C語言巧用二分查找實現猜數游戲

    C語言巧用二分查找實現猜數游戲

    二分查找也稱折半查找(Binary?Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲結構,而且表中元素按關鍵字有序排列,本篇文章教你用二分查找編寫猜數字游戲
    2022-02-02
  • Qt禁止程序多開的實現示例

    Qt禁止程序多開的實現示例

    本文主要介紹了Qt 禁止程序多開的實現示例,主要介紹了三種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-09-09
  • C++靜態(tài)持續(xù)變量介紹

    C++靜態(tài)持續(xù)變量介紹

    這篇文章主要介紹了 C++靜態(tài)持續(xù)變量,靜態(tài)持續(xù)變量的定義C++和C語言是一樣的,它擁有三種鏈接性,即外部鏈接性、內部連接性和無鏈接性。其中外部鏈接性指的是可以在其他文件中訪問,內部鏈接性指的是只能在當前文件訪問,需要的朋友可以參考一下
    2021-11-11
  • C++指針學習詳解

    C++指針學習詳解

    指針在 C\C++ 語言中是很重要的內容,并且和指針有關的內容一向令初學者頭大,這篇文章主要給大家介紹了關于C/C++中指針的相關資料,需要的朋友可以參考下
    2021-09-09
  • C語言模擬擲骰子游戲

    C語言模擬擲骰子游戲

    這篇文章介紹了C語言模擬擲骰子游戲的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。需要的朋友可以收藏下
    2021-11-11
  • 一篇文章帶你了解C++的KMP算法

    一篇文章帶你了解C++的KMP算法

    這篇文章主要介紹了c++ 實現KMP算法的示例,幫助大家更好的理解和學習c++,感興趣的朋友可以了解下,希望能給你帶來幫助
    2021-08-08
  • C#將Unicode編碼轉換為漢字字符串的簡單方法

    C#將Unicode編碼轉換為漢字字符串的簡單方法

    下面小編就為大家?guī)硪黄狢#將Unicode編碼轉換為漢字字符串的簡單方法。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01

最新評論