5

I was told the reference variable must be initialized in the initialization list, but why this is wrong?

   class Foo
    {
    public: 
        Foo():x(0) {      
         y = 1;
        }
    private:
        int& x;
        int y;
    };

Because 0 is a temporary object? If so, what kind of object can reference be bound? The object which can take an address?

Thanks!

skydoor
  • 25,218
  • 52
  • 147
  • 201

3 Answers3

15

0 is not an lvalue, it's an rvalue. You cannot modify it, but you're trying to bind to a reference where it could be modified.

If you make your reference const, it will work as expected. Consider this:

int& x = 0;
x = 1; // wtf :(

This obviously is a no-go. But const&'s can be bound to temporaries (rvalues):

const int& x = 0;
x = 1; // protected :) [won't compile]

Note that the life-time of the temporary is ended at the completion of the constructor. If you make static-storage for your constant, you'll be safe:

class Foo
{
public:
    static const int Zero = 0;

    Foo() : x(Zero) // Zero has storage
    {
        y = 1;
    }
private:
    const int& x;
    int y;
};
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • The specific example in his constructor though will still fail miserably if he declares the member variable `const`. – Omnifarious Feb 09 '10 at 20:40
  • What happens when the temporary 0 is destructed? How about if its address is taken and that dereferenced? – Omnifarious Feb 09 '10 at 23:25
  • This answer is wrong, and doesn't answer the OPs question, I just tested. – Omnifarious Feb 09 '10 at 23:50
  • @Omnifarious: I'm not sure what you mean. It works as intended; you can take the address as well. – GManNickG Feb 10 '10 at 00:53
  • @GMan, look at the code I posted and the results of the program run. The reference ends up being filled with garbage, and if you put in a value other than 0 you don't get it back out again. – Omnifarious Feb 10 '10 at 01:00
  • @Omnifarious: Oh, I see what you're saying. I thought you meant my two little samples. Let me fix the answer. – GManNickG Feb 10 '10 at 01:22
  • @GMan, that is sort of true, but not completely. If you do `const int &x = 5;` inside a function and the scope of `x` is that function, 5 is guaranteed to live as long as `x` is. It's only long-lived references that will outlast the lifetime of their temporaries. – Omnifarious Feb 10 '10 at 04:35
0

A long lived reference must be bound to an lvalue. Basically, as you so eloquently put it, an object that has a definite address. If they are bound to a temporary the temporary will be destroyed while the reference is still referencing it and the results are undefined.

Short lived const references (local function variables and function arguments) can be bound to temporaries. If they are, the temporary is guaranteed to not be destroyed until the reference goes out of scope.

Demonstration code:

#include <iostream>

class Big {
 public:
   Big() : living_(true), i_(5) { // This initialization of i is strictly legal but
      void *me = this;            // the result is undefined.
      ::std::cerr << "Big constructor called for " << me << "\n";
   }
   ~Big() {
      void *me = this;
      living_ = false;
      ::std::cerr << "Big destructor called for " << me << "\n";
   }

   bool isLiving() const { return living_; }
   const int &getIref() const;
   const int *getIptr() const;

 private:
   ::std::string s_;
   bool living_;
   const int &i_;
   char stuff[50];
};

const int &Big::getIref() const
{
   return i_;
}

const int *Big::getIptr() const
{
   return &i_;
}

inline ::std::ostream &operator <<(::std::ostream &os, const Big &b)
{
   const void *thisb = &b;
   return os << "A " << (b.isLiving() ? "living" : "dead (you're lucky this didn't segfault or worse)")
             << " Big at " << thisb
             << " && b.getIref() == " << b.getIref()
             << " && *b.getIptr() == " << *b.getIptr();
}

class A {
 public:
   A() : big_(Big()) {}

   const Big &getBig() const { return big_; }

 private:
   const Big &big_;
};

int main(int argc, char *argv[])
{
   A a;
   const Big &b = Big();
   const int &i = 0;
   ::std::cerr << "a.getBig() == " << a.getBig() << "\n";
   ::std::cerr << "b == " << b << "\n";
   ::std::cerr << "i == " << i << "\n";
   return 0;
}

And the output:

Big constructor called for 0x7fffebaae420
Big destructor called for 0x7fffebaae420
Big constructor called for 0x7fffebaae4a0
a.getBig() == A living Big at 0x7fffebaae420 && b.getIref() == -341121936 && *b.getIptr() == -341121936
b == A living Big at 0x7fffebaae4a0 && b.getIref() == 0 && *b.getIptr() == 0
i == 0
Big destructor called for 0x7fffebaae4a0
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
0

Well, you can never change it, 0 can never equal anything other than 0.

try

 class Foo
    {
    public: 
        Foo(int& a):x(a) {      
         y = 1;
        }
    private:
        int& x;
        int y;
    };

Alternatively, you can do this if your reference is constant because then 0 can only ever equal zero

Steve
  • 11,763
  • 15
  • 70
  • 103
  • The reference becomes invalid once the constructor is done. :/ – GManNickG Feb 09 '10 at 20:21
  • No, the reference becomes invalid when the int passed into the constructor goes out of scope in the calling function – Steve Feb 09 '10 at 20:22
  • Yeah, post edit responses are pretty low. You should mention "I fix'd." not, "You're wrong." :( – GManNickG Feb 09 '10 at 20:27
  • Sorry GMan, after I edited I looked at the page again and your comment wasn't there. When I came back I saw your comment. – Steve Feb 09 '10 at 20:36