如何从C++11匿名函数内部访问本地变量?

42

我正在对一个向量(权重)进行简单的规范化,尝试使用STL算法使代码尽可能清晰(我意识到使用for循环非常简单):

float tot = std::accumulate(weights.begin(), weights.end(), 0.0);
std::transform(weights.begin(), weights.end(), [](float x)->float{return(x/tot);});

目前,tot对匿名函数不可见,所以这段代码无法编译。最好的方法是如何让局部变量对匿名函数可见?


抱歉,0应该是0.0!已编辑。 - bd1
3个回答

61
你需要一个闭包。
float tot = std::accumulate(weights.begin(), weights.end(), 0);
std::transform(weights.begin(), weights.end(), [tot](float x)->float{return(x/tot);});

在这种情况下,tot 是按值捕获的。C++11 lambda 支持以下方式的捕获:

  1. 按值捕获 [x]
  2. 按引用捕获 [&x]
  3. 按引用捕获当前作用域中的任何变量 [&]
  4. 同 3,但按值捕获 [=]

你可以在逗号分隔的列表中混合使用上述任意方式进行捕获 [x, &y]


太好了!有没有关于STL中C++11匿名函数用法的好的网络/书籍参考资料?我在网上找到的大部分都是过时的匿名函数解决方法或者随意的博客文章。 - bd1
1
@bd1 C++0x的维基百科实际上相当不错。此外,公众可以获得的最新标准草案名为n3424。不幸的是,目前还没有关于C++0x的书籍。 - pmr
如果你使用[=]创建一个闭包,并且它复制了封闭作用域中的所有变量,那么这是否包括全局变量?它何时停止向上遍历作用域链? - Seth Carnegie
3
@Seth,我应该更明确一些:只有在lambda体内实际使用的实体(通过&=进行默认捕获)才会被捕获。这意味着只有在访问全局变量时才会复制全局变量。请参阅§5.1.2.11中的默认捕获和§5.1.2.9中的作用域规则。 - pmr

12

lambda可以从外部作用域“捕获”变量:

[ ..., N, ... ](int a, int b) -> int  { return (a + b) * N; }
 ^^^^^^^^^^^^^  ^^^^^^^^^^^^     ^^^^
 captured vars  local params     ret.type

您可以按值或按引用捕获,并且您可以使用特殊的语法[=][&]从环境范围内捕获任何东西,即您实际上要使用的任何内容。


如果我将一个lambda表达式作为函数参数进行解析,会出现错误,提示lambda函数不是该参数的正确类型。 - Ari Seyhun
@Acidic:由于您无法说出lambda表达式的类型,因此很难拥有参数为闭包类型的函数。通常,您可以使用函数模板来推断类型,或者使用类型擦除包装器(例如std::function)来管理可调用对象的异构集合。 - Kerrek SB

3

您需要将tot添加到“捕获列表”中:

float tot = std::accumulate(weights.begin(), weights.end(), 0);
std::transform(weights.begin(), weights.end(), [tot](float x)->float{return(x/tot);});

或者,您可以使用默认捕获来隐式捕获tot

float tot = std::accumulate(weights.begin(), weights.end(), 0);
std::transform(weights.begin(), weights.end(), [=](float x)->float{return(x/tot);});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接