立即计算的 lambda 的 C++ 范围规则

use*_*014 6 c++ lambda

当我运行此 C++ 代码时:

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

int main()
{
  vector<int> vec = [&] ()
  {
    cout<<"pre vec.size() = "<< vec.size() <<endl;
    vector<int> retval = {};
    for(int i = 0; i < 10; ++i)
      vec.push_back(i); // I typed "vec", not "retval"
    
    cout<<"vec.size() = "<< vec.size() <<endl;
    cout<<"retval.size() = "<< retval.size() <<endl;
    return retval;
  }();
  
  cout<< vec.size() <<endl;
}
Run Code Online (Sandbox Code Playgroud)

我得到输出:

pre vec.size() = 34354494244
vec.size() = 10
retval.size() = 10
10
Run Code Online (Sandbox Code Playgroud)

在 lambda 内部, vec 似乎首先未初始化(请参阅大小)。为什么 push_backing 不会导致(valgrind)错误?

循环后,vec 的大小为 10,可以。但是为什么 retval 的大小也已经是 10(而不是 0)了?

由于我在 lambda 中填充了 vec,但返回了空的 retval,为什么 vec 在评估后的大小为 10(而不是 0)?

我尝试了 g++ 9.3.0 和 clang++ 10.0.0 并获得了行为。

Sam*_*hik 12

尝试对您的程序进行小幅调整。添加打印两个向量地址的语句:

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

int main()
{
  vector<int> vec = [&] ()
  {
    cout<<"pre vec.size() = "<< vec.size() <<endl;
    vector<int> retval = {};

    std::cout << &retval << " = " << &vec << std::endl;
    for(int i = 0; i < 10; ++i)
      vec.push_back(i); // I typed "vec", not "retval"

    cout<<"vec.size() = "<< vec.size() <<endl;
    cout<<"retval.size() = "<< retval.size() <<endl;
    return retval;
  }();

  cout<< vec.size() <<endl;
}
Run Code Online (Sandbox Code Playgroud)

GCC显示了相同的地址都retvalvec!这是命名返回值优化的结果,这是允许的。这就是 valgrind 无法检测到这种未定义行为的原因。未定义的行为意味着“任何事情都可能发生”,包括程序按预期工作。这就是这里发生的事情:实际的构造vec发生在retval构造时,并且由于从 lambda 返回而没有发生复制。载体已经构建好了。

但是请注意,命名返回值优化是可选的,不需要实现。您不能依赖每个编译器都以这种方式工作,而将未定义行为的原始问题放在一边。


Wol*_*DEV 7

您正在寻找的是所谓的返回值优化。

在 C++ 编程语言的上下文中,返回值优化 (RVO) 是一种编译器优化,它涉及消除为保存函数返回值而创建的临时对象。

来源(维基百科)

这意味着对于您的代码,不是retval在堆栈上分配变量,而是直接使用vec. 这就是为什么vec被隐式地“初始化”
(注意:这个语句可能有点过于简单化,但很容易想象!)。

您可以使用-fno-elide-constructors(GCC)
测试禁用此优化:https : //godbolt.org/z/bhTbjf

PS:此代码调用未定义的行为!不要这样做;)