Why is my string member variable set to an empty string in C ++?
Consider the following code:
class Foo
{
private:
const string& _bar;
public:
Foo(const string& bar)
: _bar(bar) { }
const string& GetBar() { return _bar; }
};
int main()
{
Foo foo1("Hey");
cout << foo1.GetBar() << endl;
string barString = "You";
Foo foo2(barString);
cout << foo2.GetBar() << endl;
}
When I execute this code (in VS 2013), the instance foo1
has an empty string in its member variable _bar
, and the foo2
corresponding member variable holds a reference to the value "You". Why is this?
Update: I am of course using the std :: string class in this example.
source to share
For the Foo foo1("Hey")
compiler to convert from const char[4]
to std::string
. It creates a prvalue of type std::string
. This line is equivalent:
Foo foo1(std::string("Hey"));
Link binding occurs from prvalue to bar
, and then other link binding occurs from bar
to Foo::_bar
. The problem here is what std::string("Hey")
is temporary, which is destroyed when the full expression in which it appears ends. That is, std::string("Hey")
there will be no semicolon after it .
This is causing the dangling link because you now have Foo::_bar
one referencing an instance that has already been destroyed. When you print a string, you then execute undefined behavior to use the dangling link.
The string is Foo foo2(barString)
fine because it barString
exists after initialization foo2
, so Foo::_bar
it still references a valid instance std::string
. The temporary is not created because the type of the initializer matches the type of the reference.
source to share
Yes, these are C ++ wonders and understanding:
- Object lifetime
- This string is a class and literal char arrays are not "strings".
- What happens with implicit constructors.
Anyway, string is a class, "Hey" is really just an array of characters. So when you create a Foo with "Hey" that wants a string reference, it does what is called an implicit conversion. This is because it string
has an implicit constructor from character arrays.
Now for the lifetime of the object. Having built this chain for you, where does she live and what is her lifespan. Well, actually for the meaning of this call, here is the Foo constructor and everything it calls. So it can call all sorts of functions in its entirety and this line is valid.
However, once this call is completed, the object expires. Unfortunately, you have kept the const reference in your class and you are allowed to. The compiler doesn't complain because you can keep the const reference for the object to live longer.
Unfortunately, this is a nasty trap. And I remember once I deliberately gave my constructor, which really wanted a const reference, a non-const reference in order to ensure that this situation does not occur (and does not get a temporary one) exactly. This might not be the best workaround, but it worked at the time.
The best option in most cases is to just copy the string. It's cheaper than you think if you don't really process a lot. In your case, this probably won't actually copy anything, and the compiler will secretly move the copy it did anyway.
You can also take a non-const string reference and "swap" it to
As of C ++ 11, there is another option to use move semantics, which means that the string passed to will become "received", itself invalid. This is especially useful when you want to use time series, which is your example (although mostly time series are built using an explicit constructor or return value).
source to share
The problem is that in this code:
Foo foo1("Hey");
From a string literal "Hey"
(raw char
array, more precisely const char [4]
, given the three characters in Hey and the trailing one \0
), a temporary instance is created std::string
and passed to the constructor Foo(const string&)
.
This constructor stores a reference to this temporary string in the data member const string& _bar
:
Foo(const string& bar)
: _bar(bar) { }
Now the problem is that you are keeping a reference to the temporary string. Therefore, when the temporary string "evaporates" (after the constructor call statement), the link becomes dangling, ie. Refers ("points to ...") to some garbage.
So you are carrying undefined behavior (for example, compiling your code with MinGW on Windows with g ++, I have a different result).
Instead, in this second case:
string barString = "You";
Foo foo2(barString);
the link foo2::_bar
is associated with ("points to") barString
, which is not temporary but is a local variable in main()
. This way, after the call to the constructor, barString
it still exists when you print the string using cout << foo2.GetBar()
.
Of course, to fix this, you should consider using a data item std::string
instead of a link.
This way, the string will be deeply copied into the data item, and it will persist even if the original source string used in the constructor is temporary (and "evaporates" after the constructor is called).
source to share