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

C++ Lambda捕获右值引用异常行为的原因探究

Lambda捕获右值指针导致所有包装函数指向同一变量的问题分析

问题背景

尝试通过Lambda表达式将函数包装为std::function<void()>类型,编写了VoidFunctionWrapper类。该类的wrap_function方法使用[&]引用捕获外部变量,已知此方式存在问题,应改用值捕获[=]。但测试时发现所有包装后的函数均修改同一个变量,输出的指针地址完全相同;原本预期会出现悬垂指针崩溃,但实际并未发生,现疑惑&(check_list[i_check])这个右值在Lambda中的捕获机制,以及为何会导致此现象。

相关代码

VoidFunctionWrapper类代码

class VoidFunctionWrapper
{
public:
    VoidFunctionWrapper()
    {
        function_ = []() {};
    }

    template <typename Function, typename... Args>
    VoidFunctionWrapper(Function &&function, Args &&...args)
    {
        wrap_function(std::forward<Function>(function), std::forward<Args>(args)...);
    }

    void wrap_function(std::function<void()> function)
    {
        function_ = std::move(function);
    }

    template <typename Function, typename... Args>
    std::enable_if_t<!std::is_same_v<std::decay_t<Function>, std::function<void()>>, void>
    wrap_function(Function &&function, Args &&...args)
    {
        std::function<void()> function_lambda = [&]() mutable
        { function(std::forward<Args>(args)...); };

        wrap_function(function_lambda);
    }

    std::function<void()> get_wrapped_function()
    {
        return function_;
    }
private:
    std::function<void()> function_;
};

关键成员函数

template <typename Function, typename... Args>
std::enable_if_t<!std::is_same_v<std::decay_t<Function>, std::function<void()>>, void>
wrap_function(Function &&function, Args &&...args)
{
    std::function<void()> function_lambda = [&]() mutable
    { function(std::forward<Args>(args)...); };

    wrap_function(function_lambda);
}

测试代码

int main()
{
    std::function<void(bool *)> check = [](bool *ptr)
    { 
        std::cout << ptr << std::endl;
        *ptr = true; 
    };

    std::vector<VoidFunctionWrapper> tasks_to_execute;
    std::deque<bool> check_list(5, false);
    for (size_t i_check = 0; i_check < check_list.size(); i_check++)
    {
        VoidFunctionWrapper temp_func = VoidFunctionWrapper(check, &(check_list[i_check]));
        tasks_to_execute.push_back(std::move(temp_func));
    }
    
    for (size_t i_check = 0; i_check < check_list.size(); i_check++)
    {
        tasks_to_execute[i_check].get_wrapped_function()();
    }

    for (size_t i_check = 0; i_check < check_list.size(); i_check++)
    {
        std::cout << check_list[i_check] << " ";
    }
    std::cout << std::endl;
}

测试结果

0x7f9506008804
0x7f9506008804
0x7f9506008804
0x7f9506008804
0x7f9506008804
0 0 0 0 1 

核心疑问

Lambda中捕获的右值&(check_list[i_check])是如何工作的,为何会让所有VoidFunctionWrapper实例修改同一个变量?

原因分析

  1. 引用捕获的对象是函数形参
    [&]会引用捕获Lambda所在作用域的所有外部变量,但这里的args是wrap_function的函数形参——&(check_list[i_check])这个临时指针会被绑定到bool*&&类型的形参上,Lambda捕获的是这个形参的引用,而非check_list[i_check]的地址本身。

  2. 函数形参的生命周期与栈空间复用
    wrap_function是模板函数,每次循环调用时,函数的栈帧结构一致,形参args在栈上的位置固定。当wrap_function执行完毕,形参被销毁,Lambda持有的是悬垂引用。但由于栈空间被复用,每次循环的形参都会占用同一个栈位置,最后一次循环时该位置存储的是&check_list[4]的地址,因此所有Lambda调用时都会读取到这个地址,导致输出相同,且仅最后一个元素被修改为true。

  3. 未定义行为的偶然性
    未出现崩溃是未定义行为的偶然结果——此时栈空间未被其他操作覆盖,刚好保留了最后一次的值。这种情况完全不可靠,换环境或修改代码极可能引发崩溃或异常。

解决方案

要避免悬垂引用并正确捕获参数值,需修改Lambda的捕获方式:

  • 改用值捕获[=],将function和args的值拷贝到Lambda中;
  • 或显式捕获并完美转发参数,避免捕获不必要的引用:
template <typename Function, typename... Args>
std::enable_if_t<!std::is_same_v<std::decay_t<Function>, std::function<void()>>, void>
wrap_function(Function &&function, Args &&...args)
{
    std::function<void()> function_lambda = 
        [func = std::forward<Function>(function), 
         args_tuple = std::make_tuple(std::forward<Args>(args)...)]() mutable
    {
        std::apply(std::move(func), std::move(args_tuple));
    };

    wrap_function(function_lambda);
}

内容的提问来源于stack exchange,提问作者Christ Liu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:17:37