C++11 lambda表達(dá)式在回調(diào)函數(shù)中的使用方式
在回調(diào)函數(shù)中使用lambda表達(dá)式的好處,在于可以利用C++的RAII機(jī)制來(lái)做資源的自動(dòng)申請(qǐng)釋放,避免手動(dòng)管理出錯(cuò)。
一、lambda表達(dá)式在C++異步框架中的應(yīng)用
1. 一個(gè)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ù)的第二個(gè)參數(shù)使用了lambda表達(dá)式作為參數(shù),并且閉包捕獲了self變量。這樣做的目的是通過(guò)shared_ptr增加this的引用計(jì)數(shù)。 在server::do_accept函數(shù)中存在一句代碼:
std::make_shared(std::move(socket))->start();
這里有一個(gè)表達(dá)式亡值,屬于shared_ptr類型。當(dāng)啟動(dòng)start()方法后,會(huì)為該智能指針?biāo)芾淼膶?duì)象增加一次引用計(jì)數(shù)。 所以在離開作用域后,shared_ptr析構(gòu)不會(huì)導(dǎo)致實(shí)際的session對(duì)象析構(gòu)。
最終當(dāng)不再繼續(xù)注冊(cè)異步讀寫回調(diào)時(shí)(在這里的代碼中,當(dāng)讀寫出現(xiàn)錯(cuò)誤時(shí)),即放棄該連接的session時(shí), 智能指針的引用計(jì)數(shù)降為0,觸發(fā)session對(duì)象的析構(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表達(dá)式在資源管理上帶來(lái)了傳統(tǒng)的函數(shù)指針不具備的優(yōu)勢(shì)。因?yàn)楫?dāng)回調(diào)函數(shù)被執(zhí)行時(shí),使用傳統(tǒng)寫法需要在每個(gè)條件分支下都要考慮到資源的釋放。
2. C++ http框架cutelyst在異步執(zhí)行PostgreSQL數(shù)據(jù)庫(kù)sql請(qǐng)求的例子
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ù)作用是斷開事件處理鏈,即當(dāng)這個(gè)dbp函數(shù)返回時(shí),對(duì)該context不去向?yàn)g覽器發(fā)出http響應(yīng)。代碼大致為:
ASync::ASync(Context *c)
{
c->detachAsync();
}析構(gòu)函數(shù)作用是恢復(fù)事件處理鏈,即通知eventloop,可以對(duì)該context發(fā)送http響應(yīng)了。大致為:
ASync::~ASync()
{
c->attachAsync();
}通過(guò)在異步sql執(zhí)行函數(shù)中注冊(cè)一個(gè)lambda表達(dá)式,lambda表達(dá)式捕獲一個(gè)外部變量,利用RAII機(jī)制,能夠?qū)崿F(xiàn)在異步sql執(zhí)行完畢后再進(jìn)行http響應(yīng)。這是lambda表達(dá)式閉包捕獲變量的優(yōu)勢(shì)。
二、如何在C-style注冊(cè)回調(diào)函數(shù)中使用lambda表達(dá)式?
有一個(gè)c庫(kù),其中存在一個(gè)注冊(cè)回調(diào)函數(shù):
void register_callback(void(*callback)(void *), void * context);
希望可以注冊(cè)C++11的lambda表達(dá)式,而且是帶捕獲變量的lambda表達(dá)式,因?yàn)橐貌东@變量來(lái)維持狀態(tài)。
首先分析一下這個(gè)注冊(cè)函數(shù):
這個(gè)注冊(cè)回調(diào)函數(shù)第一個(gè)參數(shù)是C-style函數(shù)指針,不帶狀態(tài)。第二個(gè)參數(shù)void *context ,攜帶函數(shù)執(zhí)行的狀態(tài)。
這樣每次函數(shù)執(zhí)行的時(shí)候,會(huì)將context傳遞進(jìn)來(lái),做到了持續(xù)保持對(duì)狀態(tài)的訪問(wèn)和修改。
void register_callback( void(*callback)(void*), void * p ) {
//這里是一個(gè)簡(jiǎn)單的模擬。實(shí)際中可能會(huì)多次調(diào)用callback函數(shù)。
callback(p); // 測(cè)試
callback(p);
}對(duì)于將lambda表達(dá)式與函數(shù)指針之間的轉(zhuǎn)換,如果沒(méi)有捕獲變量可以直接轉(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風(fēng)格,充滿了類型不安全。如果想要使用lambda表達(dá)式來(lái)直接捕獲變量x,則不行。下面這個(gè)代碼無(wú)法通過(guò)編譯。
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表達(dá)式轉(zhuǎn)換成普通函數(shù)指針,同時(shí)能夠保留狀態(tài)呢?
方法一: 聲明一個(gè)全局的invoke_function函數(shù),將lambda表達(dá)式轉(zhuǎn)為為void*,即將lambda表達(dá)式作為狀態(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表達(dá)式的所有權(quán),狀態(tài)都存在這里。此處使用的是棧變量,可以根據(jù)實(shí)際的需要變成堆變量,防止cpp_function析構(gòu)后再使用,成為undefined behavior。
方法二:使用模板,將狀態(tài)存在一個(gè)結(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;
};這個(gè)模板類callback,第一個(gè)成員就是普通函數(shù)指針,用于注冊(cè)回調(diào)函數(shù)時(shí)使用。第二個(gè)成員是自定義deleter的unique_ptr,智能指針管理的是一個(gè)匿名類(即lambda表達(dá)式所屬的類)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
MATLAB全網(wǎng)最全的colormap的使用教程詳解
眾所周知,MATLAB中的colormap只有少得可憐的幾種,有很多應(yīng)用在很特殊的圖形中的colormap幾乎都沒(méi)有,而每次寫代碼都要去找顏色的圖屬實(shí)太麻煩。所以本文將包全部集成了進(jìn)來(lái),終于有了這套包含200個(gè)colormap的工具函數(shù),希望對(duì)大家有所幫助2023-02-02
C語(yǔ)言關(guān)于注釋的知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家分享的是關(guān)于C語(yǔ)言關(guān)于注釋的知識(shí)點(diǎn)總結(jié),需要的朋友們可以參考學(xué)習(xí)下。2020-02-02
C語(yǔ)言巧用二分查找實(shí)現(xiàn)猜數(shù)游戲
二分查找也稱折半查找(Binary?Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲(chǔ)結(jié)構(gòu),而且表中元素按關(guān)鍵字有序排列,本篇文章教你用二分查找編寫猜數(shù)字游戲2022-02-02
C#將Unicode編碼轉(zhuǎn)換為漢字字符串的簡(jiǎn)單方法
下面小編就為大家?guī)?lái)一篇C#將Unicode編碼轉(zhuǎn)換為漢字字符串的簡(jiǎn)單方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01

