網站首頁 編程語言 正文
在回調函數中使用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表達式所屬的類)。
原文鏈接:https://blog.csdn.net/BlowfishKing/article/details/119788184
相關推薦
- 2022-05-07 Qt+OpenCV實現目標檢測詳解_C 語言
- 2022-05-04 redis?zset實現滑動窗口限流的代碼_Redis
- 2022-10-21 Rust?入門之函數和注釋實例詳解_相關技巧
- 2024-01-07 使用 SpringSecurity 發送POST請求出現 403
- 2022-04-17 C語言?動態內存管理全面解析_C 語言
- 2022-05-04 Spring.Net框架簡介_實用技巧
- 2023-01-30 如何使用Flutter實現手寫簽名效果_Android
- 2022-04-18 python?request?post?列表的方法詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支