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?
When you pass a string literal to a function that accepts const std::string&
, the following events occur:
const char*
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.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.