1

I'm having some weird behaviour with the following:

using namespace std;
struct Number
{
    Number(int init) : a(init) {}
    Number() {};
    int a;
    static Number& getNumber() { return Number(555); }

    //Number(const Number& other)
    //{
    //  a = other.a;
    //}  // I've commented this out you'll see why
};

int main()
{
    Number num1;  // Is now junk
    Number num2;  // Is now junk
    num2 = Number::getNumber(); // num 2 is still junk

    Number num3 = Number::getNumber(); // num3 has been assigned 555. 
                                       // If I define my own copy constructor
                                       // (uncomment it), it stays junk.
    cout << num3.a;
}

When I make my own copy constructor, whether it takes a const or not, the value coming in in the "other" argument is junk. I don't get this behaviour if the copy constructor is the default one. I tried it on IDEOne using GCC and this code doesn't compile. However on my Visual Studio it runs as I described.

I find it really hard to understand the rules of how long a temporary is still valid. For example, I thought that if getNumber() returns a reference to a local temporary, it's OK if it's assigned directly on the same line. I was wrong.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • 1
    Your static function returns a temporary object *by reference*. So it is destroyed before it is used. – Galik Sep 27 '17 at 13:43
  • If gcc wont compile just stop and listen to its error messages. MSVS has a ["feature"](https://stackoverflow.com/questions/16380966/non-const-reference-bound-to-temporary-visual-studio-bug) that allows this to compile. – NathanOliver Sep 27 '17 at 13:43
  • @NathanOliver This is truly evil, I want this warning – Zebrafish Sep 27 '17 at 13:49
  • @Zebrafish: Use the `/Za` flag. If you're in the IDE, go to the main menu `Project->Properties->C/C++->Language->Disable Language Extensions->Yes` – Benjamin Lindley Sep 27 '17 at 13:55
  • For simple value-types like `Number`, in general you should pass them around by value, not by reference. (Note that the **copy constructor** must take its argument by reference; otherwise you get an infinite recursion) – Pete Becker Sep 27 '17 at 13:57
  • @Benjamin Lindley Oh nothing will compile now, it's got stuff like illegal declaration of anonymous struct in wingdi.h, whatever that is. – Zebrafish Sep 27 '17 at 13:59
  • @Zebrafish Beware that disabling language extensions in MSVS will prevent platform specific headers such as `windows.h` from compiling. Disabling the language extensions is a great idea unless you are doing something Windows-specific. – François Andrieux Sep 27 '17 at 14:01
  • @Francois Andrieux No I'm doing an OpenGL project, but all the other windows headers seem to be dependencies for some reason. The compiling errors are still applying to them. – Zebrafish Sep 27 '17 at 14:07

3 Answers3

2

getNumber has undefined behavior. You are returning a reference to a local object. When the function returns that object is destroyed so now you have a reference to a object that no longer exists. To fix this we can just return by value like

static Number getNumber() { return {555}; }

And now you the number that is returned is directly constructed from the return value.

All function local variables are destroyed after the return value is created but before execution proceeds. That means returning any type of reference or pointer to a local object will leave you with a dangling reference/pointer.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • No I knew that num2 uses the assignment operator in that case. So, I knew the temporary local gets destroyed, but I thought that if it gets assigned directly on the same line it'll assign the correct value. I'm wrong about this? In other words, the local is no good the very second it returns, whether it's assigned or not. – Zebrafish Sep 27 '17 at 13:53
  • @Zebrafish Yes. The function local argument gets destroyed as soon as you get the reference to assign to `num2` with. Temporary values that are created in an expression last until the end of the full expression but that does not cover function local arguments. Those are always destroyed when the function returns and before the execution proceeds. – NathanOliver Sep 27 '17 at 13:58
1

I learned something trying to answer this.

What are the possible ways you could return an object from your static function?

By value

This is usually the correct way. Often the compiler will use a Return-Value-Optimisation or otherwise avoid actually copying the object multiple times. If you're not sure this is probably what you want.

If the default copy constructor isn't sufficient for you, make sure you define your own, remembering to define it to take a const ref argument. If you're using C++11, defining a separate move constructor may be useful.

By non-const reference

This is incorrect because it's a reference (effectively a pointer) to a memory location that used to contain a variable which doesn't exist any more.

It's an error in gcc. It used to be allowed in Visual Studio although I hear it may not be any more. You can compile with compiler option /Za to turn off various microsoft specific extensions if you want to.

By const reference

This is not an error, but is a warning and is undefined behaviour [citation needed]

You can bind a const ref to a temporary object. See Herb Sutter's article: https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

e.g. "const Number& num = get_number_by_value()" will often return a copy of a temporary object, and elide the copy, and then the temporary object will be bound to the reference and have its lifetime extended in way that works specifically for const ref (but not other ref, or pointers).

However, I just learned now looking it up, that this technically applies to returning a temporary from a function, but that lifetime is not further lengthened if that is then assigned to another const ref.

So your case

Number num = get_number_by_const_ref()

may work ok but

const Number& num = get_number_by_const_ref()

may not.

Return a const reference to a static member variable

This is not usually helpful, but if your class is very expensive to construct (requires a lot of calculation or uses GB of memory) and you want to return a particular instance of it multiple times, you might have a private const static member variable of the class which stores an instance you can return by ref.

Remember, if you have a static member variable containing an instance variable of the class, it needs to be initialised outside the class in a .c file so the constructor function is available.

Jack V.
  • 1,381
  • 6
  • 20
  • You **cannot** return a function local variable by const ref – NathanOliver Sep 27 '17 at 23:32
  • Thank you. Mea culpa. Do you know which bit of the standard to quote? – Jack V. Sep 28 '17 at 09:57
  • Not off hand, it is covered by the scoping and lifetime rules. [Here](https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope) is a great post about it though. – NathanOliver Sep 28 '17 at 11:35
0

The function static Number& getNumber() { return Number(555); } creates a temporary Number and returns a reference to it. The temporary object ceases to exist at the end of the function, meaning the reference you are returning now refers to a destroyed object. This is undefined behavior, which means the behavior could be anything, including appearing to work sometimes. The compiler is not required to diagnose this error, but some (such as GCC) do. If you intend to return a mutable reference to a shared instance, declare a static local object in the body of the function and return a reference to it.

static Number& getNumber() 
{ 
    static Number my_instance{555}; 
    return my_instance;
}
François Andrieux
  • 28,148
  • 6
  • 56
  • 87