在线程中使用可调用对象创建线程的跨平台问题求助
我之前也踩过这个一模一样的坑!不管是Windows下的Visual Studio还是Linux下的GCC,都会遇到这个问题,核心原因是std::thread的构造逻辑:它需要接管你传入的可调用对象的所有权,默认行为是尝试拷贝这个对象,但你的noncopyable类明确禁用了拷贝构造和赋值运算符,自然就触发编译错误了。
问题拆解
先抓你的代码里的关键矛盾点:
noncopyable类删除了拷贝构造/赋值运算符,只保留了移动构造的声明thread_starter::start函数接收的是左值引用(callable & o)
当你把这个左值引用传给std::thread的构造函数时,它会默认尝试拷贝这个对象,而拷贝被明确禁用,不管哪个平台的编译器都会直接报错。
解决方案
这里有两种靠谱的解决思路,你可以根据自己的业务场景选择:
1. 用完美转发让std::thread移动对象
把start函数的参数改成万能引用(universal reference),然后用std::forward做完美转发,这样就能触发对象的移动构造(前提是你的noncopyable已经正确实现了移动构造),彻底避开拷贝操作。
修正后的thread_starter代码:
class thread_starter { public: template<typename callable> bool start(callable&& o) { // 万能引用,兼容左值和右值 std::thread t(std::forward<callable>(o)); // 完美转发,触发移动构造 // 这里根据你的需求处理线程,比如存入容器、join或者detach t.detach(); return true; } };
同时记得补全noncopyable的移动构造实现(你的代码里只声明了,没定义):
noncopyable::noncopyable(noncopyable&& other) : number(other.number) { other.number = nullptr; // 把原对象的指针置空,避免重复释放内存 }
2. 用std::ref包装引用(务必注意生命周期!)
如果你不想移动对象,想让线程直接使用原对象的引用,可以用std::ref包装传入的对象。但一定要确保原对象的生命周期绝对长于线程,否则会出现悬空引用,导致程序崩溃或未定义行为。
用法示例:
class thread_starter { public: template<typename callable> bool start(callable& o) { std::thread t(std::ref(o)); // 用std::ref包装,让线程持有对象引用 t.detach(); return true; } };
⚠️ 重点提醒:如果线程还在运行时,o被销毁了,线程访问它会直接出问题,这种方案只适合对象生命周期明确可控的场景。
为什么Windows和Linux都报错?
因为std::thread是C标准库的组件,不管哪个平台的编译器,都必须遵循C标准的规定:构造线程时默认要获取可调用对象的副本(除非你用引用包装明确指定)。所以只要禁用了拷贝,又没处理移动或引用,就会触发一致的编译错误。
内容的提问来源于stack exchange,提问作者Yunsheng Guo

