当我运行此 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显示了相同的地址都retval和vec!这是命名返回值优化的结果,这是允许的。这就是 valgrind 无法检测到这种未定义行为的原因。未定义的行为意味着“任何事情都可能发生”,包括程序按预期工作。这就是这里发生的事情:实际的构造vec发生在retval构造时,并且由于从 lambda 返回而没有发生复制。载体已经构建好了。
但是请注意,命名返回值优化是可选的,不需要实现。您不能依赖每个编译器都以这种方式工作,而将未定义行为的原始问题放在一边。
您正在寻找的是所谓的返回值优化。
在 C++ 编程语言的上下文中,返回值优化 (RVO) 是一种编译器优化,它涉及消除为保存函数返回值而创建的临时对象。
这意味着对于您的代码,不是retval在堆栈上分配变量,而是直接使用vec. 这就是为什么vec被隐式地“初始化”
(注意:这个语句可能有点过于简单化,但很容易想象!)。
您可以使用-fno-elide-constructors(GCC)
测试禁用此优化:https : //godbolt.org/z/bhTbjf