Реализация функции промежуточного программного обеспечения с использованием функций-членов с помощью lambdas/bind
У меня есть функция, Post()
, что принимает два аргумента - std::string
путь для прослушивания запросов и std::function<void(Request &, Response &)>
обрабатывать входящий запрос. Обратите внимание, что я не могу изменить Post()
,
Например:
m_server.Post("/wallet/open", [this](auto req, auto res)
{
std::cout << req.body << std::endl;
}
Я пытаюсь передать запрос через функцию промежуточного программного обеспечения, а затем перейти к функции обработчика.
Функция-обработчик и функция промежуточного программного обеспечения являются функциями-членами. Настройка привязки Post() происходит внутри функции-члена того же класса.
Это работает:
m_server.Post("/wallet/open", [this](auto req, auto res){
auto f = std::bind(&ApiDispatcher::openWallet, this, _1, _2);
middleware(req, res, f);
});
Однако я подключаю разумное количество запросов, и было бы неплохо абстрагировать это в функцию, поэтому мы можем просто вызвать, например,
m_server.Post("/wallet/open", router(openWallet));
Мне удалось заставить что-то подобное работать, но я не могу понять, как заставить это работать при использовании функций-членов. Это прекрасно работает, если все это бесплатная функция:
const auto router = [](auto function)
{
return std::bind(middleware, _1, _2, function);
};
m_server.Post("/wallet/open", router(openWallet))
.Post("/wallet/keyimport", router(keyImportWallet))
.Post("/wallet/seedimport", router(seedImportWallet))
.Post("/wallet/viewkeyimport", router(importViewWallet))
.Post("/wallet/create", router(createWallet))
Однако моя попытка заставить его работать для функций-членов не работает, и я не могу понять, что говорит мне сообщение об ошибке:
const auto router = [](auto function)
{
return std::bind(&ApiDispatcher::middleware, _1, _2, function);
};
m_server.Post("/wallet/open", router(&ApiDispatcher::openWallet))
.Post("/wallet/keyimport", router(&ApiDispatcher::keyImportWallet))
.Post("/wallet/seedimport", router(&ApiDispatcher::seedImportWallet))
.Post("/wallet/viewkeyimport", router(&ApiDispatcher::importViewWallet))
.Post("/wallet/create", router(&ApiDispatcher::createWallet))
Функция роутера сама по себе работает. Я думаю, что проблема в том, что я не вызываю функции обработчика на this
, Я попытался сделать это вместо этого:
m_server.Post("/wallet/open", router(std::bind(&ApiDispatcher::openWallet, this, _1, _2)));
Но затем я получаю сообщение об ошибке "Неверное количество аргументов для указателя на член".
Вот минимальный пример, чтобы повторить проблему:
#include <iostream>
#include <string>
#include <functional>
using namespace std::placeholders;
class ApiDispatcher
{
public:
void middleware(std::string req, std::string res, std::function<void(std::string req, std::string res)> handler)
{
// do some processing
handler(req + " - from the middleware", res);
}
void dispatch()
{
const auto router = [](auto function)
{
return std::bind(&ApiDispatcher::middleware, _1, _2, function);
};
/* Uncommenting this line breaks compilation */
// Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
void openWallet(std::string req, std::string res)
{
std::cout << req << std::endl;
}
void Post(std::string method, std::function<void(std::string req, std::string res)> f)
{
// wait for a http request
f("request", "response");
}
};
int main()
{
ApiDispatcher server;
server.dispatch();
}
Благодарю. Извините, пост был таким длинным.
4 ответа
Ваша функция маршрутизатора должна вернуть функцию, которая будет вызывать указанную функцию-член с определенным экземпляром класса.
void dispatch()
{
const auto router = [this](auto function) {
return std::bind(function, this, _1, _2);
};
Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
Или, и я упустил это изначально, чтобы все маршрутизировать через функцию промежуточного программного обеспечения, проще всего было найти.
void dispatch()
{
const auto router = [this](auto function) {
return [this, function](auto a, auto b) {
middleware(a, b, std::bind(function, this, _1, _2));
};
};
Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
Или, если вы предпочитаете использовать связывание, то сначала вам нужно заставить внутреннюю привязку быть функцией, и сделайте.
void dispatch()
{
const auto router = [this](auto function) {
std::function<void(std::string, std::string)> func = std::bind(function, this, _1, _2);
return std::bind(&ApiDispatcher::middleware, this, _1, _2, func);
};
Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
Мне легче рассуждать с лямбдами, и из того, что я понимаю, они предпочтительнее std::bind
по причинам производительности.
void dispatch()
{
Post("/wallet/open", [&](std::string req_1, std::string res_1){
middleware(req_1, res_1, [&](std::string req_2, std::string res_2){
openWallet(req_2, res_2);
});
});
}
Каждый вызов функции-члена здесь заключен в лямбду. Вы, вероятно, должны использовать const std::string&
в качестве аргументов, чтобы избежать копий для всех вызовов.
Еще одна вещь, чтобы указать на производительность. Пока вы только переадресовываете вызовы функций (вы не сохраняете вызываемый объект для последующего использования), вы можете изменить middleware
а также Post
к шаблонам. Вызываемым будет параметр шаблона, и вам не придется платить за std::function
,
template <typename F>
void middleware(std::string req, std::string res, F handler)
{
// do some processing
handler(req + " - from the middleware", res);
}
template <typename F>
void Post(std::string method, F f)
{
// wait for a http request
f("request", "response");
}
редактировать
Чтобы извлечь часть логики так, как я думаю, вы имеете в виду, вы передаете указатель на член-функцию.
void dispatch()
{
auto const router = [&](auto m_fptr) {
return [&, m_fptr](std::string req, std::string res) {
middleware(req, res, [&](std::string req_2, std::string res_2) {
(this->*m_fptr)(req_2, res_2);
});
};
};
Post("/wallet/open", router(&ApiDispatcher::openWallet));
}
Корень проблемы из-за параметра на router
лямбда и как std::bind
внутри лямбда будет интерпретировать это.
давайте предположим, что вы исправили звонок router
внутри dispatch
, заменяя:
Post("/wallet/open", router(&ApiDispatcher::openWallet));
по соответствующей форме:
Post("/wallet/open", router(std::bind(&ApiDispatcher::openWallet, this, _1, _2)));
затем по ссылкеfunction
параметр лямбда router
будет выведено как связующее выражение и, как следствие, std::bind
внутри router
увидим, что этот параметр удовлетворяет std::is_bind_expression<T>::value == true
и нет преобразования в std::function
будет предпринята попытка при вызове middleware
,
Чтобы это исправить, нужно явно указать тип function
параметр на router
лямбда, как:
const auto router = [this](std::function<void(std::string req, std::string res)> function)
{
return std::bind(&ApiDispatcher::middleware, this, _1, _2, function);
};
Делая это таким образом, неявное преобразование
std::bind(&ApiDispatcher::openWallet, this, _1, _2)
в std::function
происходит перед звонком
std::bind(&ApiDispatcher::middleware, this, _1, _2, function)
или же вы сохраняете auto
и запустить неявное преобразование внутри лямбды:
const auto router = [this](auto function)
{
std::function<void(std::string req, std::string res)> func = function;
return std::bind(&ApiDispatcher::middleware, this, _1, _2, func);
};
Не уверен... но мне кажется, что твоя идея была написать это или что-то подобное
const auto router = [this](auto function)
{
std::function<void(std::string req, std::string res)> fn
= std::bind(function, this, _1, _2);
return std::bind(&ApiDispatcher::middleware, this, _1, _2, fn);
};
Но я предлагаю следовать лямбда-внутри-лямбда-решению, предложенному super.