向functor类型vector推入lambda引发构造函数无限调用
问题:自定义function类的转换构造函数无限递归调用
现象描述
自行实现了function<R(Args...)>类,在向std::vector<function<int()>>中push_backlambda表达式时,预期lambda会转换为function类型,却触发转换构造函数无限递归调用。简化测试后发现,注释掉以下任意一项代码,程序即可正常运行:
- 内存分配语句:
mem_ptr_ = static_cast<void*>(allocator_.new_object<Cb>(std::forward<Cb>(fn))); - 显式析构函数:
~function() {} operator()的返回语句:return R{};
示例代码
#include <concepts> #include <cstdio> #include <memory_resource> template <typename Fn, typename R, typename... Args> concept invocable_r = std::is_invocable_r<R, Fn, Args...>::value; template <typename R, typename... Args> class function; template <typename R, typename... Args> class function<R(Args...)> { public: using allocator_type = std::pmr::polymorphic_allocator<std::byte>; auto get_allocator() { return allocator_; } template <invocable_r<R, Args...> Cb> function(Cb&& fn, allocator_type allocator = {}) : allocator_{ allocator } { printf("Converting constructor invoked!\n"); // Comment this out mem_ptr_ = static_cast<void*>(allocator_.new_object<Cb>(std::forward<Cb>(fn))); } // Or this ~function() {} auto operator()(Args... args) { // or this return R{}; } private: allocator_type allocator_; void* mem_ptr_ = nullptr; }; int main() { using foo_t = function<int()>; std::vector<foo_t> myvec; myvec.push_back([]() -> int { printf("Hello World1!\n"); return 10; }); }
运行输出
Converting constructor invoked! Converting constructor invoked! Converting constructor invoked! ... (无限循环)
原因分析
核心问题是自定义function类自身满足invocable_r<R, function<R(Args...)>, Args...>概念,导致模板构造函数错误地匹配到自身类型,触发无限递归:
push_backlambda时,首先触发lambda到function的转换,调用模板构造函数。- 由于
function定义了operator()返回R,自身是可调用对象,满足invocable_r约束,模板构造函数允许接受function类型的参数。 - 当vector内部需要移动/拷贝
function对象时(比如扩容或构造元素),因为没有显式声明移动/拷贝构造函数,编译器生成的默认构造函数优先级低于模板构造函数,导致模板构造函数被再次调用,传入function自身,形成递归循环。
注释掉那几项代码能解决的原因:
- 注释
operator()返回:function不再是可调用对象,不满足invocable_r约束,模板构造函数无法匹配自身类型,编译器转而使用默认移动构造函数。 - 注释显式析构函数:编译器会自动生成默认移动构造函数,其优先级高于模板构造函数,避免递归调用。
- 注释内存分配代码:避免了分配过程中可能触发的
function对象移动/构造操作,间接终止递归。
解决方案
方案1:给模板构造函数添加约束,排除自身类型
通过requires约束禁止模板构造函数接受function自身类型:
#include <type_traits> // ... 其他代码不变 ... template <invocable_r<R, Args...> Cb> requires (!std::same_as<std::decay_t<Cb>, function>) function(Cb&& fn, allocator_type allocator = {}) : allocator_{ allocator } { printf("Converting constructor invoked!\n"); mem_ptr_ = static_cast<void*>(allocator_.new_object<Cb>(std::forward<Cb>(fn))); }
方案2:显式声明移动/拷贝构造函数
显式定义移动构造函数,让其优先级高于模板构造函数:
// ... 其他代码不变 ... // 移动构造函数 function(function&& other) noexcept : allocator_{ std::move(other.allocator_) }, mem_ptr_{ other.mem_ptr_ } { other.mem_ptr_ = nullptr; // 避免重复释放内存 } // 根据需求禁用或实现拷贝构造 function(const function& other) = delete; function& operator=(const function& other) = delete; function& operator=(function&& other) noexcept = default;
验证修改
修改后的代码运行时仅会输出一次Converting constructor invoked!,无限递归问题解决。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

