You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

向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...>概念,导致模板构造函数错误地匹配到自身类型,触发无限递归:

  1. push_backlambda时,首先触发lambda到function的转换,调用模板构造函数。
  2. 由于function定义了operator()返回R,自身是可调用对象,满足invocable_r约束,模板构造函数允许接受function类型的参数。
  3. 当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 21:30:23