如何将wrapThreadHelper转为wrapThread内的Lambda并保持完美转发语义?
用Lambda封装std::thread并保留完美转发语义
问题
我要封装std::thread,让新线程在执行用户指定的函数前先跑一些预处理逻辑。现在用辅助函数wrapThreadHelper实现了,但总觉得太麻烦,想把这部分逻辑直接写到wrapThread函数里的Lambda里,同时还要保留完美转发语义,不能破坏参数的左值/右值属性。
现有实现代码
#include <functional> #include <iostream> #include <string> #include <thread> using namespace std; struct Foo { void bar() { cout << "bar is running" << endl; } }; template <class F, class... Args> void wrapThreadHelper(F &&f, Args &&...args) { cout << "preparing the thread..." << endl; std::invoke(std::forward<F>(f), std::forward<Args>(args)...); cout << "cleaning the thread..." << endl; } template <class F, class... Args> std::thread wrapThread(F &&f, Args &&...args) { return std::thread(&wrapThreadHelper<F, Args...>, std::forward<F>(f), std::forward<Args>(args)...); } int main() { Foo foo; std::thread t1 = wrapThread(&Foo::bar, std::ref(foo)); std::thread t2 = wrapThread([] { cout << "lambda is running..."; }); t1.join(); t2.join(); return 0; }
想要改成的样子
我原本想写成这样,但编译通不过,而且不知道怎么保留完美转发:
template <class F, class... Args> std::thread wrapThread(F &&f, Args &&...args) { return std::thread([]() { cout << "preparing the thread..." << endl; std::invoke(std::forward<F>(f), std::forward<Args>(args)...); cout << "cleaning the thread..." << endl; }); }
解决办法
你写的这个Lambda版本有两个关键问题:一是Lambda没捕获外部的f和args,直接用肯定报错;二是就算捕获了,直接用std::forward也没法正确保留参数的原始值类别,毕竟Lambda是在新线程里执行的,参数的生命周期和值属性得妥善传递。
下面两种方案可以解决问题,还能保留完美转发:
方案一:C++20及以上版本用转发捕获
C++20支持在Lambda的初始化捕获里用std::forward,直接把参数按原始值类别转移到Lambda内部,代码最简洁:
template <class F, class... Args> std::thread wrapThread(F&& f, Args&&... args) { return std::thread( // 用初始化捕获+forward保留参数的左值/右值属性 [f = std::forward<F>(f), args = std::forward<Args>(args)...]() mutable { cout << "preparing the thread..." << endl; // 再次forward,把正确的值类别传递给用户函数 std::invoke(std::forward<F>(f), std::forward<Args>(args)...); cout << "cleaning the thread..." << endl; } ); }
这里要加mutable,因为Lambda默认是const的,而我们需要修改捕获的参数(比如要移动右值参数),不加的话没法用std::forward转发右值。
方案二:兼容C++17的写法
如果要支持C++17,可以把参数打包成tuple,再用std::apply展开调用:
template <class F, class... Args> std::thread wrapThread(F&& f, Args&&... args) { return std::thread( // 把函数和参数打包,用forward保留值类别 [func = std::forward<F>(f), params = std::make_tuple(std::forward<Args>(args)...)]() mutable { cout << "preparing the thread..." << endl; // 用apply展开tuple,完美转发每个参数 std::apply( [&func](auto&&... inner_args) { std::invoke(std::forward<F>(func), std::forward<decltype(inner_args)>(inner_args)...); }, std::move(params) ); cout << "cleaning the thread..." << endl; } ); }
这种写法通过tuple把所有参数打包移动到Lambda里,再用std::apply逐个展开转发,同样能保证参数的原始语义。
测试验证
修改后的wrapThread完全兼容原来的测试代码,不管是调用类成员函数、Lambda还是普通函数,都能正确执行预处理和清理逻辑,同时不会产生不必要的拷贝,完美保留了参数的左值/右值属性。
内容的提问来源于stack exchange,提问作者Tirafesi
相关产品推荐
相关产品推荐

