向boost::signals2::signal添加不可拷贝类型连接失败
问题原因
两个因素共同导致编译错误,分别是包装逻辑的bug和boost::signals2的固有设计约束:
- 包装逻辑缺陷:你使用了
std::forward_as_tuple构造tuple,该函数无论传入左值还是右值,都会生成存储引用的tuple。传入左值Widget时,生成tuple<Widget&>存储左值引用,这类tuple的拷贝构造是合法的(仅复制引用本身,不拷贝对象),因此场景正常;传入右值临时Widget时,生成tuple<Widget&&>存储右值引用,C++标准规定包含右值引用成员的类默认拷贝构造函数是隐式删除的,这就是报错里提到的copy constructor of '_Head_base' is implicitly deleted because field '_M_head_impl' is of rvalue reference type 'Widget &&'的直接来源。 - boost::signals2设计约束:该库底层基于
boost::function实现槽的类型擦除存储,要求传入的可调用对象必须支持拷贝构造——即使你调用connect时传入右值用std::move转移,库内部为了实现连接管理、线程安全的槽复制/传递逻辑,仍然会校验可调用对象的拷贝构造能力,移动-only的可调用对象无法直接传入。
修复方案
针对两个问题分别修正即可:
- 替换tuple构造逻辑:不再使用
std::forward_as_tuple,直接构造std::tuple<Handler>持有处理器。模板参数Handler会根据传入参数的值类别自动推导:传入左值时推导为左值引用类型,tuple存储引用无额外拷贝;传入右值时推导为非引用的值类型,tuple通过移动构造持有处理器对象,不会触发拷贝构造。 - 适配不可拷贝类型的拷贝要求:对于持有不可拷贝处理器的移动-only lambda,用
std::shared_ptr包装,shared_ptr的拷贝仅增加引用计数,不会触发底层对象的拷贝,可满足boost::signals2对槽的可拷贝要求。
修复后的完整add函数实现如下:
template<class Handler> void add(Handler&& handler) { // 按值类别构造tuple:左值存引用,右值移动构造存值 auto handler_tuple = std::tuple<Handler>(std::forward<Handler>(handler)); // 用shared_ptr包装lambda,使其满足可拷贝要求 auto wrapper = [handler_ptr = std::make_shared<decltype(handler_tuple)>(std::move(handler_tuple))]() mutable { std::invoke(std::get<0>(*handler_ptr)); }; sig.connect(std::move(wrapper)); }
修改后传入左值、右值不可拷贝对象均可正常编译运行,不会触发Widget的拷贝构造。
内容的提问来源于stack exchange,提问作者bielu000
相关产品推荐
相关产品推荐

