views:

1040

answers:

4

I have some questions on returning a reference to a local variable from a function:

class A
{
public:
        A(int xx):x(xx)
 {
  printf("A::A()\n");
 }
};

const A& getA1()
{
 A a(5);
 return a;
}

A& getA2()
{
 A a(5);
 return a;
}

A getA3()
{
 A a(5);
 return a;
}

int main()
{ 
     const A& newA1 = getA1(); //1
     A& newA2 = getA2();       //2
     A& newA3 = getA3();       //3
}

My questions are =>

  1. Is the implementation of getA1() correct? I feel it is incorrect as it is returning the address of a local variable or temporary.

  2. Which of the statements in main (1,2,3) will lead to undefined behavior?

  3. In const A& newA1 = getA1(); does the standard guarantee that a temporary bound by a const reference will not be destroyed until the reference goes out of scope?

+2  A: 

Q1: Yes, this is a problem, see answer to Q2.

Q2: 1 and 2 are undefined as they refer to local variables on the stack of getA1 and getA2. Those variables go out of scope and are no longer available and worse can be overwritten as the stack is constantly changing. getA3 works since a copy of the return value is created and returned to the caller.

Q3: No such guarantee exists to see answer to Q2.

dharga
+12  A: 

1. Is getA1() implementation correct ? I feel it is incorrect as it is returning address of local variable or temporary.

The only version of 'getA?' that is correct in your program is "getA3". Both of the others have undefined behaviour no matter how you use them later.

2. Which of the statements in main ( 1,2,3) will lead to undefined behavior ?

In one sense none of them. For 1 and 2 the undefined behaviour is as a result of the bodies of the functions. For the last line, 'newA3' should be a compile error as you cannot bind a temporary to a non const reference.

3. In const A& newA1 = getA1(); does standard guarantees that temporary bound by a const reference will not be destroyed until the reference goes out of scope?

No. The following is an example of that:

A const & newConstA3 = getA3 ();

Here, 'getA3()' returns a temporary and the lifetime of that temporary is now bound to the object 'newConstA3'. In other words the temporary will exist until 'newConstA3' goes out of scope.

Richard Corden
I think the last statement is confusing..you mean the temporary will not be destroyed until newConstA3 goes out of scope?
Naveen
yes .
jalf
+2  A: 

I think the main problem is that you are not returning temporaries at all, you should

return A(5);

rather than

A a(5);
return a;

Otherwise you are returning local variable address, not temporary. And the temporary to const reference only works for temporaries.

I think its explained here: temporary to const reference

Arkaitz Jimenez
I don't think this is right. The concept of temporary doesn't propagate through the return type at all. Consider that you may be calling a function in different translation unit and the caller cannot know if the return expression was a temporary or not.
Richard Corden
Even if you were returning temporaries, this will fail. Test the following code: 'struct X { ~X(){ std::cout << "~X" << std::endl; } }; X const } int main() { X const std::cout << "still alive?" << std::endl; }' the program will show that the lifetime of the temporary _inside_ f() does not extend to the lifetime of the tempoary in main().
David Rodríguez - dribeas
http://herbsutter.wordpress.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
Arkaitz Jimenez
@dribeas check this: Only one X object is constructed and it is a temporary.struct X { ~X(){ std::cout << "~X" << std::endl; }};X f() { return X();}int main() {const X std::cout << "still alive?" << std::endl;}
Arkaitz Jimenez
@Arkaitz, however not necessarily only one object. The temporary returned by `f()` is allowed to be copied before being bound to `tmp`. In that case, the copied object will be destroyed early, and only the copy will have lengthened lifetime.
Johannes Schaub - litb
@Arkaitz: I think I did not make my point clear, my bad. The problem is with the function signature returning a reference. The fact that the element in the return statement is a temporary or not does not affect the incorrectness of the code in the question.
David Rodríguez - dribeas
... now, with the change you made: the function does no longer return a reference, but a value object, then the code becomes correct. The difference being in that the temporary in the _calling_ function will be bound to the reference in the _calling_ function and get it's lifetime extended, while with the initial signature the temporary is in the _called_ function. Think of it: at the point of call, the compiler does not know whether the returned const reference is a reference to a temporary or not.
David Rodríguez - dribeas
...if the compiler were to allow the temporary to be bound, it would have to reserve extra space inside the _calling_ activation frame for the real object (not the reference), and it would not even know what the object size is. Consider: 'struct X {}; struct Y : public X { int ch; }; struct Z : public X { double d; }; X const void g() { X const }' Now, how can the compiler without knowing the f() definition decide what the real type of the returned object reference is? How could it determine what destructor to call?
David Rodríguez - dribeas
...or how much extra stack space must be reserved for the unknown temporary, or even if the returned object is a temporary or not. -- Going back to your example, you are returning a variable, the code is creating a temporary _inside_ the function, copying it to the return variable and then binding it to a reference in the _caller_ function. The fact that the compiler can elide the copy (and thus you see only a destructor call) does not mean that the semantics are not those of a copy. You can check it by declaring the copy constructor of X as private. Your code will not compile.
David Rodríguez - dribeas
@litb: In fact it is the other way around. It is not that the temporary is allowed to be copied, but rather that the compiler is allowed to elide the copy. The result is the same: different implementations could copy or not, but the compiler must check that the copy can be performed to fulfill the semantic requirements, even if at the end the copy is not performed.
David Rodríguez - dribeas
Wow, well done @dribeas.
Arkaitz Jimenez
@dribeas i haven't indicated that the compiler is allowed to proceed even if the copy constructor is inaccessible. Still, the compiler is allowed to copy - some in SO say that it isn't (because MSVC seem to behave different and because C++0x doesn't allow the implementation to do a copy anymore), so i have developed the language of "the compiler is allowed to copy" instead of "the compiler may elide the copy". The result is the same, as you say. But thanks for the clarification on my comment, i see i was only communicating half the truth.
Johannes Schaub - litb
A: 

If you will compile this on VC6 you will get this warning

**Compiler Warning (level 1) C4172 returning address of local variable or temporary A function returns the address of a local variable or temporary object. Local variables and temporary objects are destroyed when a function returns, so the address returned is not valid.**

While testing for this problem i found interesting thing (given code is working in VC6):

 class MyClass
{
 public:
 MyClass()
 {
  objID=++cntr;
 }
MyClass& myFunc()
{
 MyClass obj;
 return obj;
}
 int objID;
 static int cntr;
};

int MyClass::cntr;

main()
{
 MyClass tseadf;
 cout<<(tseadf.myFunc()).objID<<endl;

}
sat
Try this: MyClass cout << "Hello World\n"; cout << o.objID << endl; And anyway, can we move on from VC6 now. Although old doesn't always mean bad, in the case of VC6 it does.
sbk
thank for reply, Regarding VC6 , we are bounded to management people :)
sat