I have a method which returns a string to display as an error message. Depending on where this error occurs in the program, I might add a bit more of an explanation to the error message before displaying it.
string errorMessage() {
return "this is an error";
}
// somewhere in the program...
const char* message = ("Some extra info \n" + errorMessage()).c_str();
cout << message << endl;
(I am storing the message as a const char* since I will actually provide this error to another method which accepts const char* arguments)
At this point it outputs garbage (unprintable characters on the console).
So I played with it and found that if instead I do:
// somewhere in the program...
const char* message = ("Some extra info \n" + errorMessage()).c_str();
cout << ("Some extra info \n" + errorMessage()).c_str() << endl << message << endl;
then it displays the message correctly twice.
Why does providing the extra argument to cout
cause it to work as I intended?
("Some extra info \n" + errorMessage())
is a temporary std::string
. That means, after the statement is finished, it's lifetime has ended.
cout << ("Some extra info \n" + errorMessage()).c_str() << endl
works because at the point std::cout
uses the std::string
its lifetime hasn't ended yet. The
<< message
part is undefined behavior, though. Sheer luck it works.
To fix the problem, you need to extend the std::string
's lifetime with either a const std::string&
or, since C++11, a std::string&&
:
const std::string& str_const_ref = "Some extra info \n" + errorMessage();
std::string&& str_rvalue = "Some extra info \n" + errorMessage();
Now you can operate on them as you want to.
Another way is to
std::string str = "Some extra info \n" + errorMessage();
However, if the compiler doesn't do some Return Value Optimization, this will lead to a constructor and a copy constructor (< C++11, very bad) or move constructor (>= C++11, better, but unnecessary) getting executed.
BTW, this exact issue is even covered in "The C++ Programming Language" 4th Edition!
In §10.3.4 "Temporary Objects", Mr Stroustrup writes:
The standard-library string has a member
c_str()
(§36.3) that returns a C-style pointer to a zero-terminated array of characters (§2.2.5, §43.4). Also, the operator+
is defined to mean string concatenation. These are useful facilities for strings. However, in combination they can cause obscure problems. For example:void f(string& s1, string& s2, string& s3) { const char* cs = (s1+s2).c_str(); cout << cs; if (strlen(cs=(s2+s3).c_str())<8 && cs[0]=='a') { // cs used here } }
[...] A temporary string object is created to hold
s1+s2
. Next, a pointer to a C-style string is extracted from that object. Then – at the end of the expression – the temporary object is deleted. However, the C- style string returned byc_str()
was allocated as part of the temporary object holdings1+s2
, and that storage is not guaranteed to exist after that temporary is destroyed. Consequently,cs
points to deallocated storage. The output operationcout<<cs
might work as expected, but that would be sheer luck. A compiler can detect and warn against many variants of this problem. The problem with theif
-statement is a bit more subtle. The condition will work as expected because the full expression in which the temporary holdings2+s3
is created is the condition itself. However, that temporary is destroyed before the controlled statement is entered, so any use ofcs
there is not guaranteed to work.
So, do not worry about your C++ skills. Even the C++ bible explains it. ;-)
const char* message = ("Some extra info \n" + errorMessage()).c_str();
cout << message << endl;
errorMessage()
returns a temporary std::string
object
Concatenating with "Some extra info \n" + errorMessage()
creates another temporary object.
Taking c_str of it returns a pointer to its internal buffer (not a copy).
And then the temporary object is deleted, and the pointer invalid.
Everything else is undefined. It may give the correct output, crash, or do anything else.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With