我正在学习 C++ 中的 Lambda 表达式,尽管我不是 C/C++ 的新手。我很难看到使用 Capture-Clause 与在 Argument-List 中传递的老式参数将变量绘制到 Lambda 主体中进行操作的相对优点。我熟悉它们的语法差异以及每种方法中允许和不允许的内容,但只是看不出一个比另一个更有效吗?
如果您有内幕消息,或者对 Lambdas 的情况有更好的了解,请告诉我。
非常感谢,礼萨。
我正在学习 C++ 中的 Lambda 表达式,尽管我不是 C/C++ 的新手。我很难看到使用 Capture-Clause 与在 Argument-List 中传递的老式参数将变量绘制到 Lambda 主体中进行操作的相对优点。我熟悉它们的语法差异以及每种方法中允许和不允许的内容,但只是看不出一个比另一个更有效吗?
如果您有内幕消息,或者对 Lambdas 的情况有更好的了解,请告诉我。
非常感谢,礼萨。
考虑到 lambda 基本上只是函子的语法糖。例如
int x = 1;
auto f = [x](int y){ return x+y; };
或多或少等同于
struct add_x {
int x;
add_x(int x) : x(x) {}
int operator()(int y) const { return x+y; }
}
int x = 1;
add_x f{x};
当您传递 lambda 时,差异变得明显,例如
template <typename F>
void foo(F f) {
for (int i=0;i<10;++i) std::cout << f(i) << '\n';
}
像这样的函数是使用 lambdas 的主要动机之一,它是(在这种情况下仅隐式地)指定预期签名的函数。您可以调用foo
为
foo(f);
但是,如果您的仿函数/ lambda 也将x
作为参数,那么您将无法将其传递给foo
.
TL;DR:捕获的变量构成 lambda 的状态,而参数就像普通函数参数一样。
不同之处在于相同的捕获可以与不同的参数一起使用。
考虑以下简单示例
#include <iostream>
#include <iterator>
#include <algorithm>
int main()
{
int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const int N = 10;
for ( const auto &item : a ) std::cout << item << ' ';
std::cout << '\n';
std::transform( std::begin( a ), std::end( a ), std::begin( a ),
[=]( const auto &item ) { return N * item; } );
for ( const auto &item : a ) std::cout << item << ' ';
std::cout << '\n';
return 0;
}
程序输出为
0 1 2 3 4 5 6 7 8 9
0 10 20 30 40 50 60 70 80 90
lambda 的参数由算法 std::transform 提供。该算法无法将乘数 N 传递给 lambda。因此您需要捕获它,乘数将与传递给 lambda 的任何参数一起使用。
一个 lambda 表达式创建一个类似函数的对象,带有一些可选的附加状态。调用签名由 lambda 参数确定,附加状态由捕获子句确定。
现在,您需要创建的签名并不总是您的选择。如果您将 lambda 传递给标准或第三方 API,则 API 要求您的 lambda 具有特定签名。如果 tgere 是除了强加的签名之外要传递的任何数据,则需要捕获它。
考虑 C 库中一个众所周知的示例:qsort 函数。
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
比较器函数接受指向被比较的两个对象的指针,就是这样。没有办法传递一个额外的标志来控制比较是如何完成的。例如,考虑根据在运行时确定的该语言的排序规则对某种自然语言中的单词列表进行排序。你如何告诉你的比较器使用哪种语言?此 API 的唯一选项是在静态变量 (yikes) 中设置语言。
由于这个众所周知的缺点,人们正在定义各种非标准的替代 API。例如
void qsort_r(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *, void *),
void *arg);
我希望你能认识到发生了什么。您将附加参数(语言标识符或其他)作为 传递arg
,然后排序函数将其作为密封包转发给您的比较器。然后它将参数转换为其原始类型并使用它
输入 C++。在std::sort
中,比较器是一个类似对象的函数,带有自己的状态。所以这个技巧是不必要的。你定义了类似的东西
struct LanguageSensitiveComparator
{
LanguageSensitiveComparator(LangauageID lang) : lang(lang) {}
LangauageID lang;
bool operator()(const string& a, const string& b) const { .... } // etc
};
sort(dict.begin(), dict.end(), LanguageSensitiveComparator(lang));
C++11 更进一步。现在您可以使用 lambda 在现场定义函数对象。
sort (begin(dict), end(dict),
[=lang](const string& a, const string& b) { .. });
回到你的问题。您可以将 lang 作为参数传递而不是捕获它吗?当然可以,但是您需要定义自己的排序,该排序知道额外的 LabguageID 参数(这就是 qsort_r 基本上所做的,除了它不是类型安全的)。