C++使用std::thread调用类成员函数Compress报C2893、C2672错误
我在实现Threads类,用于调度20个执行具体任务的Thread类实例。Thread类包含核心方法Compress以及线程状态属性(如是否繁忙标识),Threads类将所有Thread实例存储在名为threads的std::vector容器中。
程序单线程调用逻辑可正常运行,但在Threads实现文件中使用std::thread调用Thread类的Compress方法时,编译阶段出现报错。核心相关代码如下:
Thread.h
class Thread { public: BOOL isBusy = FALSE; Threads *threadParent; Thread(); ~Thread(); BOOL Compress() { return prvCompress(); } private: BOOL prvCompress(); };
Threads.h
class Threads { public: std::vector<Thread> threads; File *myFile; Threads(File *myFile_); ~Threads(); BOOL CallCompress() { return prvCallCompress(); } private: BOOL prvCallCompress(); };
Threads.cpp
BOOL Threads::prvCallCompress() { //for (UIN8 i = 0; i < ThreadsNumber; i++) // std::thread(threads[i].Compress()).detach(); std::thread(this->threads.at(0).Compress()).detach(); return FALSE; }
编译错误信息
Error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&) noexcept(<expr>)' 报错位置:...\VisualStudio\VC\Tools\MSVC\14.32.31326\include\thread 第51行 Error C2672: 'invoke': no matching overloaded function found 报错位置:...\VisualStudio\VC\Tools\MSVC\14.32.31326\include\thread 第55行
已完成排查
- 不使用
std::thread直接调用Compress方法,可正常运行无报错 - 改用lambda作为
std::thread入口,在线程内访问threads[0]的成员变量可正常执行,返回结果符合预期,对应代码如下:
std::thread([&] { std::cout << threads[0].wasntUsed << std::endl; }).detach();
- 仅创建
std::thread实例传入Compress调用、不执行detach操作,依然触发相同编译错误,对应代码如下:
std::thread(this->threads.at(0).Compress());
你写的std::thread(this->threads.at(0).Compress())存在语法逻辑错误:这行代码会先在当前线程直接执行Compress()方法,拿到它的BOOL类型返回值,再把这个整数值传给std::thread的构造函数。std::thread构造函数要求第一个入参必须是可调用对象(函数指针、lambda、重载了()运算符的对象等),BOOL类型显然不满足可调用要求,因此编译器会报std::invoke找不到匹配重载的错误。
你直接调用Compress能跑通、lambda里访问成员能跑通,都是因为这两种场景没有把Compress的返回值当成可调用对象传给thread构造,逻辑是对的。
有两种正确的写法可以实现新线程调用Compress方法:
- 传成员函数指针+对应实例地址
非静态成员函数的调用必须绑定具体的对象实例,给std::thread传参时需要先传成员函数指针,再传对应的对象指针:// 单个线程启动写法 std::thread(&Thread::Compress, &this->threads.at(0)).detach(); // 批量启动循环写法示例 for (size_t i = 0; i < threads.size(); i++) { std::thread(&Thread::Compress, &threads[i]).detach(); } - 用lambda包裹调用逻辑(推荐,可读性更强,不易写错)
就是你之前验证过可行的lambda写法,把Compress调用放到lambda函数体内即可:// 单个线程启动写法 std::thread([this]() { threads.at(0).Compress(); }).detach(); // 批量启动循环写法示例,注意按值捕获索引避免悬空引用 for (size_t i = 0; i < threads.size(); i++) { std::thread([this, i]() { threads[i].Compress(); }).detach(); }
额外注意事项
你当前使用detach()分离线程,需要保证线程执行的整个周期内,threads容器内的Thread对象不会被销毁,也不要触发vector扩容(扩容会导致原有对象内存地址变化),否则会触发野指针访问的未定义行为。如果对生命周期管控没有把握,建议用join()等待线程执行完成再退出作用域。
内容的提问来源于stack exchange,提问作者user15044282

