将 lambda 作为参数传递 - 通过引用或值?

Posted

技术标签:

【中文标题】将 lambda 作为参数传递 - 通过引用或值?【英文标题】:passing lambda as argument - by reference or value? 【发布时间】:2017-07-11 10:04:12 【问题描述】:

我编写了一个模板代码,它接受一个函子作为参数,经过一些处理后,执行它。尽管其他人可能会将该函数传递给 lambda、函数指针甚至 std::function,但它主要用于 lambda(不是我禁止其他格式)。我想问我应该如何取那个 lambda - 按值?引用?或者别的什么。

示例代码-

#include <iostream>
#include <functional>
using namespace std;

template<typename Functor>
void f(Functor functor)

    functor();


void g()

    cout << "Calling from Function\n";


int main() 

    int n = 5;

    f([]()cout << "Calling from Temp Lambda\n";);

    f([&]()cout << "Calling from Capturing Temp Lambda\n"; ++n;);

    auto l = []()cout << "Calling from stored Lambda\n";;
    f(l);

    std::function<void()> funcSTD = []()  cout << "Calling from std::Function\n"; ;
    f(funcSTD);

    f(g);

在上面的代码中,我可以选择其中任何一个 -

template<typename Functor>
    void f(Functor functor)

template<typename Functor>
    void f(Functor &functor)

template<typename Functor>
    void f(Functor &&functor)

什么是更好的方法,为什么?这些有什么限制吗?

【问题讨论】:

恕我直言,按值传递。 我一直看到仿函数模板对象按值传递,但我不知道为什么;好问题。 相关/欺骗:***.com/questions/8196345/… 【参考方案1】:

作为一个可能的缺点,请注意,如果 lambda 不可复制,则无法通过复制传递。如果你能侥幸逃脱,通过副本就可以了。 举个例子:

#include<memory>
#include<utility>

template<typename F>
void g(F &&f) 
    std::forward<F>(f)();


template<typename F>
void h(F f) 
    f();


int main() 
    auto lambda = [foo=std::make_unique<int>()]();

    g(lambda);
    //h(lambda);

在上面的 sn-p 中,lambda 不可复制,因为 foo。由于 std::unique_ptr 的复制构造函数被删除,它的复制构造函数被删除。 另一方面,F &amp;&amp;f 接受左值和右值引用,因为它是转发引用以及 const 引用。 换句话说,如果您想多次重用相同的 lambda 作为参数,如果您的函数通过复制获取对象并且您必须移动它,因为它不可复制(好吧,实际上您可以,这只是将它包装在一个通过引用捕获外部的 lambda 中)。

【讨论】:

@NathanOliver 当然,但你不能再重用 lambda。如果你想将它传递给示例中的两个函数,这是不可能的。 @skypjack 后续问题 - 除了定义两个类似的重载之外,是否有可能在这样的场景中回退到可移动函子.. @AbhinavGauniyal 通过定义两个执行完全相同工作的重载,只能不使用转发引用?我没有得到确切的好处。我是不是误会了什么? @skypjack 我的意思是有两个h() 的定义,其中第一个是值,第二个是&amp;&amp;.. 这样编译就不会失败。 @Nik-Lz 不是最好的定义,但接近现实。 ;-)【参考方案2】:

由于 lambda 表达式可以有自己的字段(如类),因此复制/使用引用会导致不同的结果。这是一个简单的例子:

template<class func_t>
size_t call_copy(func_t func) 
    return func(1);


template<class func_t>
size_t call_ref(func_t& func) 
    return func(1);


int main() 
    auto lambda = [a = size_t0u] (int val) mutable 
        return (a += val);
    ;
    lambda(5);

    // call_ref(lambda); – uncomment to change result from 5 to 6
    call_copy(lambda);

    std::cout << lambda(0) << std::endl;

    return 0;

顺便说一句,如果你想通过性能来判断它实际上没有区别,lambda 非常小并且易于复制。

另外——如果你想将 lambda 作为参数(不是包含它的变量)传递,你应该使用转发引用以便它可以工作。

【讨论】:

“由于 lambda 表达式可以有自己的字段”和“lambda 非常小且易于复制”是两个相互矛盾的陈述。 lambda 有多大取决于这些字段有多大。可以创建任意大且非常难以复制的 lambda。

以上是关于将 lambda 作为参数传递 - 通过引用或值?的主要内容,如果未能解决你的问题,请参考以下文章

我应该通过 const 引用传递一个 lambda。

Javascript 是不是通过引用或值将数组传递给函数?

在 C# 中通过引用或值传递对象

通过引用或值传递的数组[重复]

Kotlin函数 ⑧ ( 函数引用 作为函数参数 | ::函数名 | 函数类型 作为函数返回值类型 )

将Lambda表达式作为参数传递并解析-在构造函数参数列表中使用Lambda表达式