使用具有lambdas / bind的成员函数来实现中间件函数

时间:2018-11-21 22:00:05

标签: c++ lambda c++17 generic-lambda

我有一个函数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 个答案:

答案 0 :(得分:2)

您的路由器函数需要返回一个函数,该函数将使用该类的特定实例来调用指定的成员函数。

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));
}

或者,如果您更愿意使用bind,则必须首先强制将内部bind用作函数,然后执行。

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));
}

答案 1 :(得分:2)

我发现对lambda进行推理更容易,并且据我了解,出于性能方面的原因,它们比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);
        });
    });
}

每次调用成员函数都在lambda中包装。 您可能应该使用const std::string&作为参数,以避免所有调用都被复制。

要指出的另一项性能。只要您仅转发函数调用(不存储可调用对象供以后使用),就可以将middlewarePost更改为模板。可调用对象将是模板参数,您将不必支付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));
}

答案 2 :(得分:1)

不确定...但是在我看来,您的想法是写这个或类似的东西

mediump

但是我建议遵循super提出的lambda-inside-lambda解决方案。

答案 3 :(得分:1)

问题的根源在于<ul class="movie-list"> <li class="movie" id="movie1"><a href="#movie1" class="title">Title: First2</a> <ul> <li>Year: 2223</li> <li>Format: VHS</li> <li>Actors: I and other</li> </ul> </li> <li class="movie" id="movie2"><a href="#movie2" class="title">Title: First2</a> <ul> <li>Year: 2223</li> <li>Format: VHS</li> <li>Actors: I and other</li> </ul> </li> <li class="movie" id="movie3"><a href="#movie3" class="title">Title: Second</a> <ul> <li>Year: 2019</li> <li>Format: DVD</li> <li>Actors: I</li> </ul> </li> <li class="movie" id="movie4"><a href="#movie4" class="title">Title: Second.0</a> <ul> <li>Year: 2019</li> <li>Format: DVD</li> <li>Actors: I.0</li> </ul> </li> <li class="movie" id="movie5"><a href="#movie5" class="title">Title: Second.1</a> <ul> <li>Year: 2019</li> <li>Format: DVD</li> <li>Actors: I.1</li> </ul> </li> <li class="movie" id="movie6"><a href="#movie6" class="title">Title: Second.2</a> <ul> <li>Year: 2019</li> <li>Format: DVD</li> <li>Actors: I.2</li> </ul> </li> </ul> lambda上的参数以及lambda内的router如何解释它。

假设您已将呼叫固定在std::bind内的router上,替换为:

dispatch

通过适当的形式:

Post("/wallet/open", router(&ApiDispatcher::openWallet));

然后通过cppreference,将推导Post("/wallet/open", router(std::bind(&ApiDispatcher::openWallet, this, _1, _2))); 的{​​{1}}参数作为绑定表达式,因此function内的router可以看到此参数满足std::bind,并且在调用router时不会尝试转换为std::is_bind_expression<T>::value == true

要解决此问题,您需要在std::function lambda上明确指定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,并在lambda内部触发隐式转换:

std::bind(&ApiDispatcher::middleware, this, _1, _2, function)