8

Say you have a class which is a global (e.g. available for the runtime of the app)

class MyClass {
  protected:
    std::string m_Value;
  public:
    MyClass () : m_Value("hello") {}
    std::string value() { return m_Value; }      
};

MyClass v1;

Using the first form gives me odd behavior when I do

printf("value: %s\n", v1.value().c_str());

It looks as though the string disappears from memory before printf can use it. Sometimes it prints value: hello other times it crashes or prints nothing.

If I first copy the string like so

   std::string copiedString = v1.value();
   printf("value: %s\n", copiedString.c_str());

things do work.

Surely there must be a way to avoid doing this with a temporary string.

Edit: So the consensus is to go with a const std::string & return value.

I know everyone says that the original code should be fine but I can tell you that I've seen MSVC 2005 on Windows CE having trouble with it, but only on the CE box. Not the Win32 cross compile.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Ron
  • 147
  • 2
  • 7
  • 5
    Can you post a compilable example? Right don't I don't see how what you're experiencing can happen. – Falmarri Jan 06 '11 at 22:11
  • 7
    The temporary `v1.value()` should live until the end of the full expression, i.e. it shouldn't be destroyed before `printf` returns. – CB Bailey Jan 06 '11 at 22:16
  • @Charles Bailey: Should it really? Isn't it just evaluated to get the regular char * from c_str()? – villintehaspam Jan 06 '11 at 22:20
  • 2
    12.2 [class.temporary] / 5 says "Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created." – CB Bailey Jan 06 '11 at 22:22
  • @villintehaspam: It "just evaluated" to call c_str, but it still lives until the end of the full expression, as all temporaries do. [Beaten by 7 seconds. :(] – Fred Nurk Jan 06 '11 at 22:23
  • What on earth does "the regular char * from c_str()" mean? – Lightness Races in Orbit Jan 06 '11 at 22:24
  • 2
    @user566129: If your code was char const* s = v1.value().c_str(); printf("%s", s);, then you could see the behavior you describe. Did you inadvertently change the meaning when posting? (This is one reason why complete, compilable examples are so important.) – Fred Nurk Jan 06 '11 at 22:25
  • Yes, the temporary std::string should not be destroyed before the printf returns. See [here](http://stackoverflow.com/questions/2506793/c-life-span-of-temporary-arguments/2506800#2506800) – Fozi Jan 06 '11 at 22:25
  • @Charles Bailey, Fred Nurk & Tomalak Geret'kal, Thanks for setting me straight. Special thanks for the ref to the standard. The char * from c_str() was referring to the return value. – villintehaspam Jan 06 '11 at 22:27
  • Don't let @litb hear you calling your class a "global" :) – John Dibling Jan 06 '11 at 22:28
  • I confirm @Charles's comment. Continue reading __C++ destruction of temporary object in an expression__ (http://stackoverflow.com/questions/1837092/c-destruction-of-temporary-object-in-an-expression). – Raphael Bossek Jan 06 '11 at 22:36
  • 1
    I've noticed that the compiler for WinCE is non-conforming in other ways. Unfortunately, it wouldn't totally surprise me if this was a compiler bug. – CB Bailey Jan 06 '11 at 22:43
  • Erm, surely if this is C++, what you should really do is: `cout << v1.value() << endl;`? ;) – Nim Jan 06 '11 at 22:49
  • @John i did hear it and I don't think I oppose the use. There doesn't seem to by any user defined namespace around it. – Johannes Schaub - litb Jan 06 '11 at 23:37
  • Note that MSVC has a history of not completely following the standards ... – fuzzyTew Jan 07 '11 at 00:00
  • @fuzzy: All compilers have the same history. – John Dibling Jan 07 '11 at 01:30

4 Answers4

6

Your code should work fine. Something else is wrong, that we can't detect from this testcase. Perhaps run your executable through valgrind to search for memory errors.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
1

Well, there's nothing wrong with the code (as I interpret it). It is not optimal and surely not The Right Way (R), you should modify your code like villentehaspam suggests. As it is now, your code makes a copy of the string m_value because you return by value, which is not as good as only returning a const reference.

If you provide a complete code sample that shows the problem, I could help you better.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • Compile with a compiler already implementing C++1x's move semantics and a std lib taking advantage of this (latest VC and GCC suffice), and that copy should be turned into a very cheap move. – sbi Jan 06 '11 at 22:36
  • stuck with MSVC2005 due to Windows CE development (can you see my hair go gray?) – Ron Jan 06 '11 at 22:41
  • Compiler first attempts to elide copy constructor before moving the string. AFAIK, even old VC2005 will do that easily in this case. – Gene Bushuyev Jan 06 '11 at 22:52
  • @Tomalak: I see no chance the next C++ standard could get finished by 2009, so unless you're talking about 210x, I'd rather stick to the considerably more probable "C++1x". – sbi Jan 07 '11 at 00:20
  • @sbi You're assuming that the committee's unofficial name for the standard is based solely on the proposed year of ratification, which is not the case. They still call it C++0x, for a few reasons, including to avoid confusion and because most of the work was done in the 2000s. Bjarne also continues to call it C++0x (http://www2.research.att.com/~bs/C++0xFAQ.html). – Lightness Races in Orbit Jan 07 '11 at 01:30
  • @Tomalak: This is one case where I disagree with Bjarne. It will likely be C++11 or C++12. For the digit not known yet, I substitute an "x". (And the hex argument doesn't fly, since "0x" is just a prefix.) I can see that those involved in the process, having referred to it as C++0x for a decade, find it confusing to have to change, but for those who learn about it now, the factual wrongness of the old name is certainly as least as confusing. (Maybe I'm biased, because I have taught C++ for so long and learned to bend double to be understood. But even if so, I consider this a genuine reason.) – sbi Jan 07 '11 at 09:07
  • Well, I meant that there's other, more complicated cases where the compiler would not be able to optimize, it is mostly always better to return const references... AFAIK, it doesn't hurt, and also shows the programmers intent to code a getter-function, nothing else. – rubenvb Jan 07 '11 at 09:13
  • @rubenvb: Exactly what I thought. But then: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ – sbi Jan 07 '11 at 10:20
  • @sbi: I suppose it's a case of "each to their own". I see no reason to arbitrarily change the name of the proposal, though; that the next standard will now likely have [again, an informal] name of C++12 doesn't seem altogether to be a good reason to change the informal name of the draft. – Lightness Races in Orbit Jan 07 '11 at 10:21
  • @sbi: Oh, and whilst I don't subscribe to it personally, the hex argument most certainly does fly. Just as you said, 'x' stands for an unknown digit; that the combination "0x" happens to look like the traditional hex value prefix is complete co-incidence and ultimately immaterial. The "x" is said, by these people, to have gone from standing for "9" to standing for "a" and, now, to standing for "b". Likely, in the end, standing for "c". (I do think it's silly, as we don't use hexadecimal anywhere in cases like this, but the argument most certainly does "fly".) – Lightness Races in Orbit Jan 07 '11 at 10:24
  • @sbi: that article talks exclusively about argument passing, and not returning a const reference from somewhere else. I agree that standalone functions that would modify a parameter should return their value (even if it's only for readability and program structure), but accessing a data member of a class should return a const reference. – rubenvb Jan 07 '11 at 10:36
0

not that it matters but that std::string return in that class can use a const else wise you're just creating a copy of the member value which is a waste.

std::string value() const { return m_value; }
graphitemaster
  • 247
  • 2
  • 2
-1

Here's what's typically happening when you write:

printf("value: %s\n", v1.value().c_str());
  1. The compiler makes a temporary std::string to hold the value returned from v1.value().
  2. It calls v1.value() and puts its return value in the temporary string (exactly how it does this can vary: typically it will pass a reference to the temporary as a hidden parameter to the method. See http://en.wikipedia.org/wiki/Return_value_optimization for discussion).
  3. It calls .c_str() on the temporary std::string, it stashes the const char * result away somewhere (e.g. a register).
  4. It's now finished with the temporary std::string, so destroys it (i.e. calls its destructor, maybe frees some stack space).
  5. It passes the const char * pointer it got in step (3) as an argument to printf().

The problem is that the pointer in step 3 points to memory allocated by the temporary std::string, which gets freed when the temporary's destructor is called. This memory can be long gone by the time it gets used by printf().

Basically, any usage like the one you've shown is dangerous and should be avoided. Using the following is correct:

std::string copiedString = v1.value();
printf("value: %s\n", copiedString.c_str());

because the destructor for copiedString doesn't get called until copiedString goes out of scope, some time after printf() has returned. In fact, this is no less efficient than v1.value().c_str(), because a temporary std::string gets created in either case.

Returning a reference to a string is a fine alternative, provided that the reference remains valid for as long as the caller needs it to. So, a reference to a member variable in a long-lived object is OK; a reference to something which turns out to be temporary isn't.

Ian Harvey
  • 229
  • 1
  • 5