I'm watching this talk from cppcon17 and am trying to replicate and understand the presented bugs.
However, I'm not able to replicate/understand bug #3 in the talk (timestamp is in the link above). I've written this minimal example:
#include <iostream>
#include <map>
const std::string& getDefault(const std::map<int, std::string>& map, const int key, const std::string& defVal) {
auto pos = map.find(key);
return (pos != map.end() ? pos->second : defVal);
}
void printer(const std::string& str) {
std::cout << str << std::endl;
}
int main () {
std::map<int, std::string> myMap { {1, "dog"}, {2, "cat"}};
auto& retVal = getDefault(myMap, 3, "pizza");
printer(retVal);
return 0;
}
As I understand the problem, the temporary default value of "pizza" should not be returned by reference, because it should get deleted.
But why does this example work anyway? I was expecting an error due to a reference that is no longer valid.
Edit: I've modified the example slightly to highlight that the problem still occurs, if the temporary is on another line.
This code, i.e. std::cout << getDefault(myMap, 3, "pizza") << std::endl; is valid; the temporary will be destroyed after the full expression, i.e. after the ;, the returned reference remains valid when being passed to and printed out by operator<< of std::cout.
All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created,
EDIT
Your updated code has undefined behavior. After the ; the temporary object has been destroyed and retVal is dangled; any dereference on it leads to UB.
Related
This question already has answers here:
Will a reference bound to a function parameter prolong the lifetime of that temporary?
(4 answers)
const reference to a temporary object becomes broken after function scope (life time)
(2 answers)
Closed 4 years ago.
I was watching a video Curiously Recurring C++ Bugs at Facebook
at 14:58 at the code (see in the example I give here) he says that is is hopelessly broken. The reason is that returning reference to a temporary object should not work.
so why it works?
#include <iostream>
#include <map>
using namespace std;
const string& get_default(
const map<string,string>& map,
const string& key,
const string& dflt) {
auto pos = map.find(key);
return (pos != map.end() ?
pos->second : dflt);
}
int main()
{
map<string,string> m;
auto& value = get_default(m,"whatever","this is the default value");
cout << value << endl;
return 0;
}
I understand why I cannot return reference to a local variable (and that fails nicely when I try to print it), but I cannot make this code fail, and I dont know why.
I checked google and found that if temporary object is assigned to a reference the temporary objects lifetime will be extended (thats why it is working?).
If so why is this code so hopelessy broken?
Using: gcc 7.3.0, c++14
using the comments now I can see how to make it fail:
Thanks everyone :)
int main()
{
map<string,string> m;
auto& value = get_default(m,"whatever","this is the default value"); // const string& value = fails the same way
string a = "some long string";
cout << value << endl;//"some long string" will be printed here (this is how it fails - and shows that it is a mess)
return 0;
}
The life time of a temporary object is extended only in some contexts. The example in the posted code is not one of those contexts.
int foo() { return 10; }
...
int const& x = foo(); // Life of the temporary is extended.
However,
const int& foo() { return 10; } // Life of the temporary is not extended.
...
int const& x = foo(); // Dangling reference.
Your posted code is analogous to
const int& foo(int const& in) { return in; }
...
int const& x = foo(10);
Here, the reference is valid only inside foo. Once foo returns, the object is not alive any longer, and the reference becomes a dangling reference.
char*[] object can be passed to function version2() & version 3() by the const reference, which means s2 is a temporary object equals to "###" or "###".
But why the temporary object s2 can be returned back, while the temporary object temp cannot.
int main()
{
result = version2(input, "###");
cout << "Your string enhanced: " << result << endl;
result = version3(input, "###");
cout << "Your string enhanced: " << result << endl;
return 0;
}
// No Error
const string & version2(string & s1, const string & s2)
{
return s2;
}
// Error
const string & version3(string & s1, const string & s2)
{
string temp = s2 + s1 + s2;
return temp;
}
The point is object life time.
For the 1st case, as you said, a temporary std::string is created and passed to version2 (being bound to the parameter s2). The temporary will be destroyed after the full-expression, i.e. the whole result = version2(input, "###");. That means the reference returned by version2 remains valid, it's fine to use it for assigning to result.
All temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created,
For the 2nd case, a local object temp is created inside version3, it will be destroyed when get out of version3. That means version3 will always return a dangled reference. And using it to assign to result leads to UB. The compiler might give a diagnosis for it; but doesn't have to do.
Both versions return a reference to an object.
version2 got a reference from main and return it unmodified back.
version3 defines a local variable temp and want to return a reference to this local. But unfortunately the local does no longer exist as soon as you returned from version3. Thus you would return a reference which would have already been destroyed.
I would say it just because the compiler doesn't think version2 is dangerous.
Note that for version3, it actually gives a warning, not error (unless you tell it to).
In fact if you change it to catch result by reference, there would still no warning, but the temporary is no doubt have been destroyed. you can check the assembly (or standard), but here I reproduce it and make it easy observable. (although this would be compiler dependent)
wandbox example
If you toggle optimization, you can see output changes and no warning is ever given.
just in case, here is the test code
#include<string>
#include<iostream>
using namespace std;
// No Error
auto& f(const string & s){return s;}
int main()
{
char s[]="#";
const char* cs = s;
auto& x = f(cs);
s[0]='o';
[[maybe_unused]] auto& y = f(cs);
cout << x;
}
g++7.2 -Wall -Wextra -std=c++17
So, I am trying to use a variation of the approach from this question's answers to implement a large-ish, somewhat wide, constant tree structure in C++ (using VC++2012 with the v110 toolchain), as shown below:
#include <tuple>
#include <iostream>
struct test
{
test(const char* n, const test& n1, const test& n2):
name(n), nodes(n1, n2)
{
if (name)
std::cerr << "test() " << name << std::endl;
}
~test()
{
if (name)
std::cerr << "~test() " << name << std::endl;
}
const char* name;
std::tuple<const test&, const test&> nodes;
};
const test n = test(0, n, n);
test t = test("blah", test("bleh", n, n), n);
int main()
{
std::cerr << "get " << std::get<0>(t.nodes).name << std::endl;
return 0;
}
However, instead of outputting the expected
test() bleh
test() blah
get bleh
~test() bleh
~test() blah
or something of that ilk, I get
test() bleh
test() blah
~test() bleh
get
and then the program dies with a segfault (i.e. the Windows "this program has stopped working" box) when it tries to access the prematurely destroyed inner object. Is this my fault for assuming that making a temporary a const reference held subobject of another object would extend the lifetime in the same way a local or global named const reference would? Or is this a problem with VC++2012 not realizing that the temporary is being captured by reference in the parent object constructor when it should be doing so (i.e. not destroying the inner object until the outer object gets disposed of?)
This bit: std::get<0>(t.nodes).name is never going to work properly in your current code, whatever else is going on. That is because nodes contains references: std::tuple<const test&, const test&> instead of instances.
As a result when you pass the inner object: test("bleh", n, n), it is not copied into the nodes of the outer one. Therefore, when it goes out of scope and it is destroyed, your outer object's nodes (t.nodes) contains references pointing to the great beyond. As a result your program is going to take exception to that statement and crash with a segfault.
EDIT to add:
To clarify: the object that goes out of scope is the value of: test("bleh", n, n)
In this statement: test t = test("blah", test("bleh", n, n), n);
Because by the time t = test("blah",...) returns the inner object test("bleh",...) is gone. Note that t is actually a copy of the value of test("blah",...), the original temporary is also gone. (Well it gets more complex when you consider that the compiler may choose to optimise and do a move instead of a copy, but the semantics are as though it were a copy.)
I'm currently learning C++, and am a bit confused about the concept of returning a reference from a method. Consider the following toy example:
#include <iostream>
#include <vector>
class IntHolder {
private:
std::vector<int> d_values;
public:
IntHolder() {
d_values.push_back(1);
d_values.push_back(2);
d_values.push_back(3);
}
std::vector<int> &values() {
return d_values;
}
};
int main(int argc, char **argv) {
IntHolder h;
std::vector<int> ret_val = h.values();
std::cout << ret_val.size() << std::endl;
ret_val.push_back(4);
ret_val.push_back(5);
std::cout << h.values().size() << std::endl;
return 0;
}
This prints the following to the standard output:
3
3
Since we are returning a reference to d_values, shouldn't the object returned be the same that is stored in the instance of IntHolder, and thus when calling h.values() again, we should see a size of 5?
Since this is not the case, what is the difference between returning a reference to an object or a copy of the object? Is there a difference when you are returning from a method?
You are returning a reference from the values function, but you are not assigning that returned object to a reference variable. Instead, you are making a copy of it because you are assigning it to a new std::vector<int> named ret_val.
You want to modify your code like so, in order to capture the returned reference:
std::vector<int>& ret_val = h.values();
Or perhaps simply:
auto& ret_val = h.values();
Your method is returning a reference, but you are then assigning the result to another vector, causing a copy. To fix this, change your ret_val definition to:
std::vector<int> & ret_val = h.values();
If you return a reference, you return the same object and you are not creating a copy. Basically it seems that it is just as you did expect it:
std::cout << h.values().size() << std::endl;
h.values().push_back(4);
h.values().push_back(5);
std::cout << h.values().size() << std::endl;
Would return
3
5
The misstage is another one. Let's look at
std::vector<int> ret_val = h.values();
You discovered that ret_val is a copy of h.d_values while h.values() is returning a reference to it. We could rewrite your code above as follows:
std::vector<int> &ret_ref = h.values(); // object returned by `h.values()`
std::vector<int> ret_val = ret_ref;
If you are calling ret_ref.push_back(4), h.d_values would change. But ret_val is a copy of the returned object and will not affect h.d_values.
I've got following code:
string join(initializer_list<string_view> strings);
initializer_list is std::initializer_list and string_view isn't std::string view but very similar class with constructors from const string& and const char*.
Then I've got following invocation of join:
EXPECT_EQ("this", join({ string("this") }));
After small investigation I've found that the first element of resulting initializer list isn't "this" but "\0his". This is becouse the destructor of the temporary created by string("this") is called right after creation of temporary string_view (so it contains invalid pointers). Why is it so that the lifetime of string("this") isn't extended to the end of the full-expression EXPECT_EQ("this", join({ string("this") }));?
Edit
Ok, as you suggested there is self-contained example:
#include <iostream>
#include <string>
using namespace std;
class string_view {
public:
string_view(const string& str)
: _begin(str.data())
, _end(str.data() + str.size()) {
std::cout << "string_view(...)" << std::endl;
}
const char* _begin;
const char* _end;
};
void join(initializer_list<string_view> strings) {
std::cout << "join(...)" << std::endl;
for (auto i = 0u; i < 5; ++i) {
std::cout << int(strings.begin()->_begin[i]) << " " << strings.begin()->_begin[i] << std::endl;
}
}
int main() {
join({ string("this") });
return 0;
}
The output of this program compiled with last Visual Studio C++ (Express):
string_view(...)
join(...)
0
104 h
105 i
115 s
0
It may vary from compiler to compiler as above program is probably ill-formed.
I've investigated what is the order of calls in debugger, and there is the sequence:
main()
basic_string(const char*)
string_view(const string&)
~basic_string()
initializer_list(...)
join(...)
I would like the content of the string("this") to be available inside join function. And it is not the case, becouse `string("this") is destroyed before.
Why is the destructor of temporary string string("this") invoked before the join function is called or in other words why isn't the lifetime of string("this") extended to the end of the full-expression join({ string("this") })?
What I think might be happening here is that you are constructing your initializer_list from a reference to your string, so that the string goes out of scope when your initializer_list has finished constructing.
You see your string is not a parameter to the join() function but to the initializer_list you construct.
My guess is that the initializer_list is constructed, your string goes out of scope and then your join() function tries to access the dead string through the reference contained in the string_view.