Reputation: 111
When I run this C++ code:
#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;
}
I get the output:
pre vec.size() = 34354494244
vec.size() = 10
retval.size() = 10
10
Inside the lambda, vec seems to be uninitialized first (see the size). Why does push_backing to it not cause a (valgrind) error?
After the loop, vec's size is 10, which is okay. But why does retval also already have size 10 (and not 0)?
Since I fill vec inside the lambda, but return the empty retval, why does vec have size 10 after evaluation (and not 0)?
I tried both g++ 9.3.0 and clang++ 10.0.0 and get the behavior.
Upvotes: 6
Views: 158
Reputation: 1524
What you're looking for is called return value optimisation.
In the context of the C++ programming language, return value optimization (RVO) is a compiler optimization that involves eliminating the temporary object created to hold a function's return value.
This means for your code, that instead allocating a the variable retval
on the stack, it directly uses vec
. That's why vec
has been implicitly "initialised"
(Attention: This statement may be a bit oversimplifie but easy to imagine!).
You can disable this optimisation with -fno-elide-constructors
(GCC)
Test: https://godbolt.org/z/bhTbjf
Upvotes: 7
Reputation: 118350
Try making a small tweak to your program. Add a statement that prints the addresses of both vectors:
#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;
}
gcc shows the same address for both retval
and vec
! This is the result of named return value optimization, which is allowed. This is why valgrind fails to detect this undefined behavior. Undefined behavior means "anything can happen", including the program working as intended. Which is what happened here: the actual construction of vec
took place when retval
was constructed, and no copy occurred as a result of returning from the lambda. The vector was already constructed.
Note, however, that named return value optimization is optional, and not required to be implemented. You cannot rely on every compiler working this way, setting aside the original issue of undefined behavior.
Upvotes: 12