The first two cases are pretty clear abuse of temporaries. Based on the asker's comments they've gotten what went wrong in create1 and create2 at this point, so let's focus on create3 and why it works.
Spoiler: It doesn't.
I'm going to take a few liberties with the code to make what's happening a bit more obvious. First, we replace vector with a simple class that lets us see construction and destruction better.
struct test
{
test()
{
std::cout << "test ctor\n";
}
~test()
{
std::cout << "test dtor\n";
}
};
Now we do something similar to Data and make it use test instead of vector
struct Data {
Data(const test &data = {}) : data_(data)
{
std::cout << "data ctor\n";
}
~Data()
{
std::cout << "data dtor\n";
}
const test &data_;
void forceuse() // just to make sure the compiler doesn't get any bright ideas about
// optimizing Data out before we're through with it.
{
std::cout << "Using data\n";
}
};
And we add a bit of extra diagnostic to create3 and once again replace vector with test
Data create3(const test &data = {}) {
std::cout << "in create3\n";
return Data(data); // good
}
and do the same to main
int main() {
{ // change the scope of data3 so that we can put a breakpoint at the end of main
// and watch all of the fun
std::cout << "calling create3\n";
auto data3 = create3(); // ok
std::cout << "returned from create3\n";
data3.forceuse();
}
}
Output of this is
calling create3
test ctor
in create3
data ctor
test dtor
returned from create3
Using data
data dtor
test is created during the call to create3 and destroyed on exit from create3. It is not alive in main. If it seems to be alive in main, that's just dumb bad luck. Your friend and mine Undefined Behaviour being a jerk. Again.
test is created before Data, but also destroyed before Data leaving Data in a bad state.
Here's the above code all nicely assembled and run in an online IDE: https://ideone.com/XY30XH