C++11 lambda表達式在回調(diào)函數(shù)中的使用方式
在回調(diào)函數(shù)中使用lambda表達式的好處,在于可以利用C++的RAII機制來做資源的自動申請釋放,避免手動管理出錯。
一、lambda表達式在C++異步框架中的應(yīng)用
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函數(shù)的第二個參數(shù)使用了lambda表達式作為參數(shù),并且閉包捕獲了self變量。這樣做的目的是通過shared_ptr增加this的引用計數(shù)。 在server::do_accept函數(shù)中存在一句代碼:
std::make_shared(std::move(socket))->start();
這里有一個表達式亡值,屬于shared_ptr類型。當啟動start()方法后,會為該智能指針所管理的對象增加一次引用計數(shù)。 所以在離開作用域后,shared_ptr析構(gòu)不會導(dǎo)致實際的session對象析構(gòu)。
最終當不再繼續(xù)注冊異步讀寫回調(diào)時(在這里的代碼中,當讀寫出現(xiàn)錯誤時),即放棄該連接的session時, 智能指針的引用計數(shù)降為0,觸發(fā)session對象的析構(gòu)。
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表達式在資源管理上帶來了傳統(tǒng)的函數(shù)指針不具備的優(yōu)勢。因為當回調(diào)函數(shù)被執(zhí)行時,使用傳統(tǒng)寫法需要在每個條件分支下都要考慮到資源的釋放。
2. C++ http框架cutelyst在異步執(zhí)行PostgreSQL數(shù)據(jù)庫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的構(gòu)造函數(shù)作用是斷開事件處理鏈,即當這個dbp函數(shù)返回時,對該context不去向瀏覽器發(fā)出http響應(yīng)。代碼大致為:
ASync::ASync(Context *c) { c->detachAsync(); }
析構(gòu)函數(shù)作用是恢復(fù)事件處理鏈,即通知eventloop,可以對該context發(fā)送http響應(yīng)了。大致為:
ASync::~ASync() { c->attachAsync(); }
通過在異步sql執(zhí)行函數(shù)中注冊一個lambda表達式,lambda表達式捕獲一個外部變量,利用RAII機制,能夠?qū)崿F(xiàn)在異步sql執(zhí)行完畢后再進行http響應(yīng)。這是lambda表達式閉包捕獲變量的優(yōu)勢。
二、如何在C-style注冊回調(diào)函數(shù)中使用lambda表達式?
有一個c庫,其中存在一個注冊回調(diào)函數(shù):
void register_callback(void(*callback)(void *), void * context);
希望可以注冊C++11的lambda表達式,而且是帶捕獲變量的lambda表達式,因為要用捕獲變量來維持狀態(tài)。
首先分析一下這個注冊函數(shù):
這個注冊回調(diào)函數(shù)第一個參數(shù)是C-style函數(shù)指針,不帶狀態(tài)。第二個參數(shù)void *context ,攜帶函數(shù)執(zhí)行的狀態(tài)。
這樣每次函數(shù)執(zhí)行的時候,會將context傳遞進來,做到了持續(xù)保持對狀態(tài)的訪問和修改。
void register_callback( void(*callback)(void*), void * p ) { //這里是一個簡單的模擬。實際中可能會多次調(diào)用callback函數(shù)。 callback(p); // 測試 callback(p); }
對于將lambda表達式與函數(shù)指針之間的轉(zhuǎn)換,如果沒有捕獲變量可以直接轉(zhuǎn)換。
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"; }
調(diào)用代碼
raw_function_pointer_test();
輸出:2
但是這種轉(zhuǎn)換方式,完全屬于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"; }
那有什么方法能夠?qū)⒉东@變量的lambda表達式轉(zhuǎn)換成普通函數(shù)指針,同時能夠保留狀態(tài)呢?
方法一: 聲明一個全局的invoke_function函數(shù),將lambda表達式轉(zhuǎn)為為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"; }
調(diào)用代碼
lambda_to_function();
輸出:2
std::function cpp_function用于接管lambda表達式的所有權(quán),狀態(tài)都存在這里。此處使用的是棧變量,可以根據(jù)實際的需要變成堆變量,防止cpp_function析構(gòu)后再使用,成為undefined behavior。
方法二:使用模板,將狀態(tài)存在一個結(jié)構(gòu)體里面。
#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"; }
調(diào)用代碼
lambda_capture_template_test();
輸出:2
稍微解釋一下模板做法的含義。
template<class...Args> struct callback { void(*function)(void*, Args...)=nullptr; std::unique_ptr<void, void(*)(void*)> state; };
這個模板類callback,第一個成員就是普通函數(shù)指針,用于注冊回調(diào)函數(shù)時使用。第二個成員是自定義deleter的unique_ptr,智能指針管理的是一個匿名類(即lambda表達式所屬的類)。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MATLAB全網(wǎng)最全的colormap的使用教程詳解
眾所周知,MATLAB中的colormap只有少得可憐的幾種,有很多應(yīng)用在很特殊的圖形中的colormap幾乎都沒有,而每次寫代碼都要去找顏色的圖屬實太麻煩。所以本文將包全部集成了進來,終于有了這套包含200個colormap的工具函數(shù),希望對大家有所幫助2023-02-02C#將Unicode編碼轉(zhuǎn)換為漢字字符串的簡單方法
下面小編就為大家?guī)硪黄狢#將Unicode編碼轉(zhuǎn)換為漢字字符串的簡單方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01