使用std::move捕获Lambda与this时的编译错误修复问题
我正尝试移除类的大部分拷贝构造函数和赋值运算符,在使用Lambda时遇到问题:ThreadPool的enqueue方法接收std::function<void()>类型的Task,MyClass的process方法会根据Data对象的不同值,将捕获this和std::move(_data)的Lambda传入enqueue。但Data类只声明了移动构造/赋值运算符,编译时触发错误:std::function要求目标必须可拷贝构造。尝试添加mutable修饰Lambda,问题依旧,希望在保留std::move(_data)捕获的前提下解决编译错误。
ThreadPool代码
using Task = std::function<void()>; template<size_t S> void ThreadPool<S>::enqueue(Task _task) { { std::unique_lock<std::mutex> lock(m_mutex); m_queue.push(_task); } m_cond.notify_one(); }
MyClass代码
class MyClass { public: MyClass(); // some implementation ~MyClass(); // some implementation void process(Data &_data) { if (_data.data == 1) m_tp.enqueue([this, data = std::move(_data)] () { func1(data); }); else if (_data.data == 2) m_tp.enqueue([this, data = std::move(_data)] () { func2(data); }); else m_tp.enqueue([this, data = std::move(_data)] () { func3(data); }); } void MyClass::func1(const Data &_data) { // use of data } // func2 and func3 have the same signature private: ThreadPool<4> m_tp{}; };
编译错误信息
> /usr/include/c++/11/bits/std_function.h: In instantiation of ‘std::function<_Res(_ArgTypes ...)>::function(_Functor&&) [with _Functor = MyClass::process(Data&)::<lambda()>; _Constraints = void; _Res = void; _ArgTypes = {}]’: > /usr/include/c++/11/bits/std_function.h:439:69: error: static assertion failed: std::function target must be copy-constructible > 439 | static_assert(is_copy_constructible<__decay_t<_Functor>>::value, > /usr/include/c++/11/bits/std_function.h:439:69: note: ‘std::integral_constant<bool, false>::value’ evaluates to false > ...
解决方案
核心原因
std::function的设计要求其存储的可调用对象必须是可拷贝构造的。你的Lambda因为捕获了仅支持移动的Data对象,导致整个Lambda成为不可拷贝类型(成员Data无法拷贝),因此无法被std::function容纳。添加mutable只是让Lambda内部可以修改捕获的变量,不影响其拷贝特性,所以问题依旧。
方法1:用std::shared_ptr包裹Data(兼容C++11及以上)
将Data对象包装在std::shared_ptr中,让Lambda捕获可拷贝的智能指针,这样整个Lambda就满足std::function的拷贝要求,同时保留std::move(_data)的语义:
修改MyClass的process方法:
void process(Data &_data) { auto data_ptr = std::make_shared<Data>(std::move(_data)); if (_data.data == 1) m_tp.enqueue([this, data_ptr] () { func1(*data_ptr); }); else if (_data.data == 2) m_tp.enqueue([this, data_ptr] () { func2(*data_ptr); }); else m_tp.enqueue([this, data_ptr] () { func3(*data_ptr); }); }
方法2:使用C23的std::move_only_function(推荐C23环境)
如果你的编译器支持C++23,可以直接将ThreadPool的Task类型替换为std::move_only_function<void()>,它专门用于存储仅移动的可调用对象,无需修改Lambda代码:
修改ThreadPool的代码:
// 替换Task类型 using Task = std::move_only_function<void()>; template<size_t S> void ThreadPool<S>::enqueue(Task _task) { { std::unique_lock<std::mutex> lock(m_mutex); // 注意这里需要用std::move,因为move_only_function不可拷贝 m_queue.push(std::move(_task)); } m_cond.notify_one(); }
方法3:修改ThreadPool支持仅移动任务(自定义实现)
如果不想用智能指针且无法升级到C++23,可以修改ThreadPool的任务存储方式,用std::unique_ptr包装任务对象,避免拷贝:
修改ThreadPool代码:
// 任务队列存储unique_ptr包装的function using Task = std::unique_ptr<std::function<void()>>; template<size_t S> // 改为接收任意可调用对象,转发后包装成unique_ptr template<typename Func> void ThreadPool<S>::enqueue(Func&& func) { auto task = std::make_unique<std::function<void()>>(std::forward<Func>(func)); { std::unique_lock<std::mutex> lock(m_mutex); m_queue.push(std::move(task)); } m_cond.notify_one(); }
同时需要修改线程执行任务的逻辑,移动出队列中的任务再执行:
// 线程循环示例 void ThreadPool<S>::worker() { while (true) { std::unique_lock<std::mutex> lock(m_mutex); m_cond.wait(lock, [this] { return m_stop || !m_queue.empty(); }); if (m_stop && m_queue.empty()) break; // 移动出任务,避免拷贝 auto task = std::move(m_queue.front()); m_queue.pop(); lock.unlock(); // 执行任务 (*task)(); } }
内容的提问来源于stack exchange,提问作者DipStax

