在C ++ 11 lambda中通过引用捕获引用 [英] Capturing a reference by reference in a C++11 lambda

查看:1186
本文介绍了在C ++ 11 lambda中通过引用捕获引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑一下:

#include <functional>
#include <iostream>

std::function<void()> make_function(int& x) {
    return [&]{ std::cout << x << std::endl; };
}

int main() {
    int i = 3;
    auto f = make_function(i);
    i = 5;
    f();
}

此程序是否可以确保在不调用未定义行为的情况下输出5?

Is this program guaranteed to output 5 without invoking undefined behavior?

我了解如果通过值([=])捕获x的方式,但我不确定是否通过引用捕获来调用未定义的行为.可能是make_function返回后我将以悬挂的引用结尾,还是只要原始引用的对象仍然存在,捕获的引用是否可以保证正常工作?

I understand how it works if I capture x by value ([=]), but I am not sure if I am invoking undefined behavior by capturing it by reference. Could it be that I will end up with a dangling reference after make_function returns, or is the captured reference guaranteed to work as long as the originally referenced object is still there?

在此处寻找基于标准的明确答案:)到目前为止,在实践中效果都很好;)

Looking for definitive standards-based answers here :) It works well enough in practice so far ;)

推荐答案

保证代码可以正常工作.

在深入研究标准措辞之前:此代码有效是C ++委员会的意图.但是,据信目前的措词尚不十分清楚(实际上,对标准C ++ 14之后的错误修正破坏了使其起作用的精巧安排),因此

Before we delve into the standards wording: it's the C++ committee's intent that this code works. However, the wording as it stands was believed to be insufficiently clear on this (and indeed, bugfixes made to the standard post-C++14 broke the delicate arrangement that made it work), so CWG issue 2011 was raised to clarify matters, and is making its way through the committee now. As far as I know, no implementation gets this wrong.

我想澄清两点,因为Ben Voigt的答案包含一些事实错误,这些错误引起了一些困惑:

I'd like to clarify a couple of things, because Ben Voigt's answer contains some factual errors that are creating some confusion:

  1. 范围"是C ++中的静态词汇概念,它描述程序源代码的一个区域,在该区域中,非限定名称查找将特定名称与声明相关联.它与一生无关.参见 [basic.scope.declarative]/1 .
  2. >
  3. lambda的到达范围"规则同样是一种语法属性,该属性确定何时允许捕获.例如:

  1. "Scope" is a static, lexical notion in C++, that describes a region of the program source code in which unqualified name lookup associates a particular name with a declaration. It has nothing to do with lifetime. See [basic.scope.declarative]/1.
  2. The "reaching scope" rules for lambdas are, likewise, a syntactic property that determine when capture is permitted. For example:

void f(int n) {
  struct A {
    void g() { // reaching scope of lambda starts here
      [&] { int k = n; };
      // ...

n属于此处范围,但是lambda的到达范围不包括它,因此无法捕获它.换句话说,lambda的可达到范围是它可以达到并捕获变量的向上"范围-它可以达到封闭的(非lambda)函数及其参数,但不能达到此范围,并且捕获出现在外部的声明.

n is in scope here, but the reaching scope of the lambda does not include it, so it cannot be captured. Put another way, the reaching scope of the lambda is how far "up" it can reach and capture variables -- it can reach up to the enclosing (non-lambda) function and its parameters, but it can't reach outside that and capture declarations that appear outside.

因此,达到范围"的概念与这个问题无关.被捕获的实体是make_function的参数x,该参数在lambda的作用范围之内.

So the notion of "reaching scope" is irrelevant to this question. The entity being captured is make_function's parameter x, which is within the reaching scope of the lambda.

好的,让我们看一下有关此问题的标准措辞.根据[expr.prim.lambda]/17,只有引用由副本捕获的实体的 id-expression 会转换为lambda闭包类型的成员访问权限;引用引用捕获的实体的 id-expression 保留不变,仍然表示它们在封闭范围内应表示的同一实体.

OK, so let's look at the standard's wording on this issue. Per [expr.prim.lambda]/17, only id-expressions referring to entities captured by copy are transformed into a member access on the lambda closure type; id-expressions referring to entities captured by reference are left alone, and still denote the same entity they would have denoted in the enclosing scope.

这立即看起来很糟糕:引用x的生命周期已经结束,那么我们如何引用它呢?好吧,事实证明,几乎没有(参见下文)无法在其生命周期之外引用该引用(您可以看到它的声明,在这种情况下,它在范围内,因此可以使用,或者它是一个类成员,在这种情况下,类本身必须在其生命周期内才能使成员访问表达式有效).结果,直到最近,该标准才禁止使用引用.

This immediately seems bad: the reference x's lifetime has ended, so how can we refer to it? Well, it turns out that there is almost (see below) no way to refer to a reference outside its lifetime (you can either see a declaration of it, in which case it's in scope and thus presumably OK to use, or it's a class member, in which case the class itself must be within its lifetime for the member access expression to be valid). As a result, the standard did not have any prohibitions on using a reference outside its lifetime until very recently.

lambda措辞利用了这样一个事实,即在其生命周期之外使用引用不会受到任何惩罚,因此不需要为通过引用方式捕获的实体的访问权限给出任何明确的规则-这只是意味着您使用该实体;如果是引用,则名称表示其初始化程序.这就是保证可以一直使用到最近(包括C ++ 11和C ++ 14)的方式.

The lambda wording took advantage of the fact that there is no penalty for using a reference outside its lifetime, and so didn't need to give any explicit rules for what access to an entity captured by reference means -- it just means you use that entity; if it's a reference, the name denotes its initializer. And that's how this was guaranteed to work up until very recently (including in C++11 and C++14).

但是,您不能在引用生命周期之外提到引用并不是相当.特别是,您可以从其自己的初始化程序中,在引用之前的类成员的初始化程序中引用它,或者如果它是一个命名空间范围变量,并且可以从另一个在其之前初始化的全局变量中访问它,则可以对其进行引用.引入了 CWG 2012 来解决该问题,但无意中破坏了通过引用引用捕获lambda的规范.我们应该在C ++ 17发行之前解决此回归问题;我已经提交了美国国家机构(National Body)评论,以确保其优先级适当.

However, it's not quite true that you can't mention a reference outside its lifetime; in particular, you can reference it from within its own initializer, from the initializer of a class member earlier than the reference, or if it is a namespace-scope variable and you access it from another global that is initialized before it is. CWG issue 2012 was introduced to fix that oversight, but it inadvertantly broke the specification for lambda capture by reference of references. We should get this regression fixed before C++17 ships; I've filed a National Body comment to make sure it's suitably prioritized.

这篇关于在C ++ 11 lambda中通过引用捕获引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆