3

as you can probably guess from the title, I want to understand what exactly happens when a std::string is passed to a function as a const reference, because earlier today I ran into a few situations I didn't quite understand entirely. Here's some code:

#include <string> #include <stdio.h> struct Interface { virtual void String1(const std::string &s) = 0; virtual void String2(const std::string &s) = 0; virtual void DoSomething() = 0; }; struct SomeClass : public Interface { void String1(const std::string &s) override { s1 = s.c_str(); } void String2(const std::string &s) override { s2 = s.c_str(); } void DoSomething() override { printf("%s - %s\n", s1, s2); } private: const char *s1, *s2; }; struct AnotherClass { AnotherClass(Interface *interface) : interface(interface) { this->interface->String1("Mean string literal"); } void DoTheThing() { std::string s("Friendlich string literal"); interface->String2(s); interface->DoSomething(); } private: Interface *interface = nullptr; }; int main(int argc, char **argv) { SomeClass some_class; AnotherClass another_class(&some_class); another_class.DoTheThing(); } 

When using const char * for s1 and s2 in SomeClass the program prints Friendlich string literal - Friendlich string literal or [some rubbish] - Friendlich string literal instead of Mean string literal - Friendlich string literal as I was expecting.

When switching to std::string for s1 and s2 it works as expected, printing Mean string literal - Friendlich string literal.

What a coworker and I are guessing is that the string in the ctor of AnotherClass goes out of scope but SomeClass still has the address of the string stored because of c_str().

When using std::string instead of const char * for s1 and s2 it actually makes a copy, so going out of scope isn't a problem. Like this:

struct SomeClass : public Interface { void String1(const std::string &s) override { s1 = s; } void String2(const std::string &s) override { s2 = s; } void DoSomething() override { printf("%s - %s\n", s1.c_str(), s2.c_str()); } private: std::string s1, s2; }; 

So... what's really happening? Why doesn't it work with const char *? Why does it work with std::string?

2 Answers 2

4

When you pass a string literal to a function that accepts const std::string&, the following events occur:

  • The string literal is converted to const char*
  • A temporary std::string object is created. Its internal buffer is allocated, and initialized by copying the data from the const char* until the terminating null is seen. The parameter refers to this temporary object.
  • The function body runs.
  • Assuming the function returns normally, the temporary object is destroyed at some unspecified point between when the function returns and the end of the calling expression.

If the c_str() pointer is saved from the parameter, it becomes a dangling pointer after the temporary object is destroyed since it points into the temporary object's internal buffer.

A similar problem will occur if the function accepts std::string. The std::string object will be created when the function is called and destroyed when the function returns or soon afterward, so any saved c_str() pointer will become dangling.

If the function accepts const std::string& and the argument has type std::string, however, no new object is created when the function is called. The reference refers to the existing object. The c_str() pointer will remain valid until the original std::string object is destroyed.

Sign up to request clarification or add additional context in comments.

4 Comments

And what happens to the const char * that's created from the string literal?
@Marco The pointer itself is also a temporary object, which is destroyed at the end of the full-expression. This doesn't matter, unless you intend to keep a pointer or a reference to the pointer itself. It's like how we normally don't worry about the lifetime of 3 in the expression 2 + 3.
So... if I would change the parameter to const char * it will keep it until it's not needed anymore by SomeClass?
@Marco If you change the parameter to const char*, then that pointer value will point to the string literal. The string literal will live until the program terminates. Therefore, it is safe to make a copy of that pointer and dereference it later. However, saving a pointer to the pointer parameter itself would not be safe, since the pointer parameter would be destroyed after the function returns.
1

A char * isn't an object, it's a pointer to characters that exist in some other context. If you assign such a pointer to a temporary variable, or data contained within a temporary variable, it will be invalid when the temporary is destroyed. Using it after that point produces undefined behavior.

When you have member variables of std::string, a copy is made at the time of assignment so it doesn't matter if the temporary is destroyed or not.

3 Comments

Why does it copy though? Because the type of the member variable is std::string? But isn't the argument a reference?
@Marco a std::string always contains its own character data, so if it accepts a reference it still makes its own copy.
Thank you very much :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.