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.

+3


source to share


4 answers


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.

+10


source


You take a reference to an object that is destroyed at the end of the line with foo1

. The foo2

barString object still exists, so the reference remains valid.



+7


source


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).

+2


source


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).

+2


source







All Articles