"The road to hell is paved with global variables" -- Steve McConnell
Overloading Background
Recall our discussion of overloaded functions:We solved this problem by using overloaded functions:
int cube(int n) { return n * n * n; } int i = 8; long l = 50L; float f = 2.5F; double d = 3.14; // Works fine: 512 std::cout << cube(i) << std::endl; // May or may not work: 125000 std::cout << cube(l) << std::endl; // Not quite what we want: 8 std::cout << cube(f) << std::endl; // Not quite what we want: 27 std::cout << cube(d) << std::endl;
It will also work as expected without the user needing to chose the right function:
int cube(int n) { return n * n * n; } float cube(float n) { return n * n * n; }double cube(double n) { return n * n * n; } long cube(long n) { return n * n * n; }
What we did was to provide the compiler with additional ways in which a value could be cubed.// Works fine, calls cube(int): 512 std::cout << cube(i) << std::endl; // Works fine, calls cube(long): 125000 std::cout << cube(l) << std::endl; // Works fine, calls cube(float): 15.625 std::cout << cube(f) << std::endl; // Works fine, calls cube(double): 30.9591 std::cout << cube(d) << std::endl;
We run into similar problems with operators. That is, operators may function differently when given operands of different types. For example:
3 + 4 ==> evaluates to an int, 7 3.1 + 4.1 ==> evaluates to a double, 7.2 2.4f + 5.1f ==> evaluates to a float, 7.5 3.1 + 4 ==> evaluates to a double, 7.1
How is this possible?
int i = addInt(3, 4); double d = addDouble(3.4, 4.1); int ii = add(3, 4); // overload double dd = add(3.4, 4.1); // overload
int i = 3 + 4; // adding integers, 7 double d = 3.4 @ 4.1; // adding doubles, 7.5 double dd = 3.4 + 4.1; // adding doubles, 7.0 (integer addition)
Other operators are also already overloaded for your enjoyment:
Since the compiler can deal with built-in types, it should be able to handle arrays of built-in types as well, don't you think? A lot of students new to C/C++ try to add two arrays as shown in the code snippet below. But this won't work.int i = 10; int *p = &i; // * indicates p is a pointer, & is taking an address int &r = i; // & indicates r is a reference int x = i & 7; // & is the bitwise AND operator int j = *p; // * is used to dereference p int k = i * j; // * is used to multiply i and j k = cube(4); // () are used to call function k = j * (3 + 4); // () are used to group
int a1[5] = {1, 2, 3, 4, 5};
int a2[5] = {10, 20, 30, 40, 50};
// we want a3 to have {11, 22, 33, 44, 55}
int a3[5] = a1 + a2; // this won't compile
// add the elements one at a time
for (int i = 0; i < 5; i++)
a3[i] = a1[i] + a2[i]; // this is correct
One explanation this fails is because a1 and a2 are addresses (pointers) and you can't add two pointers this way.
Another explanation is that the '+' operator is not overloaded to add two static arrays like this.
Overloading Operators for User-Defined Types (Structs and Classes)
Suppose we have two StopWatch objects, and we want to make a third StopWatch object that represents the sum of the first two StopWatches:The StopWatch class is a simple class that keeps track of how many seconds have elapsed. (The usefulness of the class is not important). We simply want a very simple class that can be used to demonstrate operator overloading.StopWatch sw1(30); // 30 seconds StopWatch sw2(10); // 10 seconds // sw3 should be 40 seconds // but it is an error StopWatch sw3 = sw1 + sw2;
Here's what the StopWatch class looks like:
| Class definition | Examples |
|---|---|
|
StopWatch sw1; StopWatch sw2(625); StopWatch sw3(9, 30, 0); sw1.Display(); // 00:00:00 sw2.Display(); // 00:10:25 sw3.Display(); // 09:30:00 sw1.Increment(); // add 1 sec sw1.Increment(); // add 1 sec sw1.Increment(5); // add 5 secs sw1.Display(); // 00:00:07 |
What is sizeof(Stopwatch)?
This code:
produces this error:// Add two StopWatches StopWatch sw3 = sw1 + sw2;
error: no match for 'operator+' in 'sw1 + sw2'
Notes:
StopWatch AddStopWatch(const StopWatch& sw1, const StopWatch& sw2)
{
// Construct a new StopWatch from two
StopWatch sw(sw1.GetSeconds() + sw2.GetSeconds());
// Return the result by value (a copy)
return sw;
}
and call it:
StopWatch sw1(30); // 30 seconds StopWatch sw2(10); // 10 seconds // Add the two and assign to third StopWatch sw3 = AddStopWatch(sw1, sw2); sw3.Display(); // prints 00:00:40
StopWatch sw1(30); // 30 seconds StopWatch sw2(10); // 10 seconds StopWatch sw3(60); // 60 seconds StopWatch sw4(20); // 20 seconds // We have to do this (and it will get worse with more) StopWatch tempsw1 = AddStopWatch(sw1, sw2); StopWatch tempsw2 = AddStopWatch(tempsw1, sw3); StopWatch sw5 = AddStopWatch(tempsw2, sw4); sw5.Display(); // 00:02:00 // We can "chain" the calls to save some typing: StopWatch sw6 = AddStopWatch(AddStopWatch(AddStopWatch(sw1, sw2), sw3), sw4); sw6.Display(); // 00:02:00
// We'd like to do this StopWatch sw5 = sw1 + sw2 + sw3 + sw4;
Overloading the binary + Operator
To overload the + operator (or most any operator for that matter), we use an operator function, which has the form:where:return-type operatorop(argument-list)
operator+(argument-list) operator-(argument-list) operator*(argument-list)
We simply need to create a function called operator+ that takes two StopWatch objects as parameters:
StopWatch operator+(const StopWatch& lhs, const StopWatch& rhs)
{
// lhs is left-hand side
// rhs is right-hand side
StopWatch sw(lhs.GetSeconds() + rhs.GetSeconds());
// Return the result by value (a copy)
return sw;
}
Compare this to our original "C-style" function:
StopWatch AddStopWatch(const StopWatch& sw1, const StopWatch& sw2)
{
// Construct a new StopWatch from two
StopWatch sw(sw1.GetSeconds() + sw2.GetSeconds());
// Return the result by value (a copy)
return sw;
}
This makes life much simpler for the users of the StopWatch class.
You can kind of think of it as the compiler re-writing your code:StopWatch sw1(30); // 30 seconds StopWatch sw2(10); // 10 seconds StopWatch sw3(60); // 60 seconds StopWatch sw4(20); // 20 seconds StopWatch sw5 = sw1 + sw2; StopWatch sw6 = sw1 + sw2 + sw3 + sw4; sw5.Display(); // 00:00:40 sw6.Display(); // 00:02:00 // Functional notation (this is really what's happening at runtime) StopWatch sw7 = operator+(sw1, sw2); sw7.Display(); // 00:00:40
In practice, the functional notation is seldom used. Essentially, overloading an operator means writing a function that the compiler will call when it sees the operator being used with a non-built-in (i.e. user-defined) data type (struct/class).StopWatch sw5 = sw1 + sw2; ====> operator+(sw1, sw2);
Again, notice how similar the functional notation is to our original function:
And these are identical:StopWatch sw7 = operator+(sw1, sw2); StopWatch sw8 = AddStopWatch(sw1, sw2);
StopWatch sw7 = sw1 + sw2; StopWatch sw7 = operator+(sw1, sw2);
Overloading More Operators in the StopWatch Class
While we're at it, we'll overload some more operators. First, the subtraction operator:
StopWatch operator-(const StopWatch& lhs, const StopWatch& rhs)
{
// Don't want to go negative
int seconds = lhs.GetSeconds() - rhs.GetSeconds();
if (seconds < 0)
seconds = 0;
// Create a new one and return it
StopWatch sw(seconds);
return sw;
}
Test it:
Simple. In a nutshell, that's about it. (Like everything in C++, there are lots of little details involved.)StopWatch sw1(1, 30, 0); // 01:30:00 StopWatch sw2(10, 0, 0); // 10:00:00 // Subtract smaller from larger StopWatch sw3 = sw2 - sw1; sw3.Display(); // 00:08:30 // Subtract larger from smaller sw3 = sw1 - sw2; sw3.Display(); // 00:00:00
How about something like this:
Of course, we'll get this:// Double the time StopWatch sw2 = sw1 * 2;
Fair enough. Another trivial function to implement:error: no match for 'operator*' in 'sw1 * 2'
// Multiply: StopWatch * int
StopWatch operator*(const StopWatch& lhs, int rhs)
{
// Multiply a StopWatch and an integer
StopWatch sw(lhs.GetSeconds() * rhs);
// Return the result
return sw;
}
// Double the time? StopWatch sw3 = 2 * sw1;
error: no match for 'operator*' in '2 * sw1' note: candidates are: StopWatch operator*(const StopWatch&, int)
// Multiply: int * StopWatch
StopWatch operator*(int lhs, const StopWatch& rhs)
{
// Multiply an integer and a StopWatch
StopWatch sw(lhs * rhs.GetSeconds());
// Return the result
return sw;
}
// Multiply: int * StopWatch
StopWatch operator*(int lhs, const StopWatch& rhs)
{
// Simply reverse the operands
return rhs * lhs;
}
Now, this multiply function will simply call the first multiply function.
How about adding an integer to a StopWatch:
| Current implementation (add 2 Stopwatches) | New expected behavior |
|---|---|
|
StopWatch sw1(60); // 00:01:00 // Add another minute? StopWatch sw2 = sw1 + 60; |
Remember these?
All operands must be the same for this to work. The fourth one is really changed to this (by your Friendly Neighborhood™ compiler):1. 3 + 4 ==> evaluates to an int, 7 2. 3.1 + 4.1 ==> evaluates to a double, 7.2 3. 2.4f + 5.1f ==> evaluates to a float, 7.5 4. 3.1 + 4 ==> evaluates to a double, 7.1
So the compiler will attempt to do this so it can use the available operator+:3.1 + (double)4
It's more obvious using a C++-style cast:StopWatch sw2 = sw1 + (StopWatch)60;
StopWatch sw2 = sw1 + (StopWatch)60; // C-style cast StopWatch sw2 = sw1 + StopWatch(60); // C++-style cast
This conversion process is mentioned in the error message from Microsoft's compiler before we implemented operator+:Note: Any constructor that requires only a single argument is called a conversion constructor and it is called implicitly by the compiler when a conversion needs to be performed.
g++ error:
MS error:error: no match for 'operator+' in 'sw1 + sw2'
binary '+' : 'StopWatch' does not define this operator or a conversion to
a type acceptable to the predefined operator
Question: Given these functions below:
// Multiply: StopWatch * int
StopWatch operator*(const StopWatch& lhs, int rhs)
{
// Multiply a StopWatch and an integer
StopWatch sw(lhs.GetSeconds() * rhs);
// Return the result
return sw;
}
// Multiply: int * StopWatch
StopWatch operator*(int lhs, const StopWatch& rhs)
{
// Simply reverse the operands
return rhs * lhs;
}
will the compiler accept the code below? (Why or why not?)
We'll have more to say on this later.StopWatch sw1(60); StopWatch sw2(10); // Multiply two StopWatches StopWatch sw3 = sw1 * sw2;
Pointers vs. References:
By now it should be very obvious why we are passing references instead of pointers with these overloaded operator functions. If we used pointers, we'd have to use the address-of operator every time we used them. For example, we'd have to do this:
StopWatch sw3 = &sw1 * &sw2;
which would be very undesirable (not to mention, illegal, as you can't multiply pointers).
Overloaded Operators as Member Functions
Up to this point, all of our overloading was done with regular functions (not part of a class). We could do this because there was a GetSeconds public method:
StopWatch operator+(const StopWatch& lhs, const StopWatch& rhs)
{
// Calls public gettor method
StopWatch sw(lhs.GetSeconds() + rhs.GetSeconds());
return sw;
}
If this method wasn't available, none of this would work.
One solution would be to make member functions: (in StopWatch.h)
class StopWatch
{
public:
// Public methods ...
// Overloaded operators (member functions) Notice the const at the end.
StopWatch operator+(const StopWatch& rhs) const;
StopWatch operator-(const StopWatch& rhs) const;
StopWatch operator*(int rhs) const;
private:
int seconds_;
};
There are two things that are different about the member functions compared to the global ones. What are they? (This is very important to realize)
Implementations: (in StopWatch.cpp)
| Original global functions | New member functions |
|---|---|
|
|
// Add the two seconds return StopWatch(lhs.GetSeconds() + rhs.GetSeconds());
All of this code works as before:Notice that the methods on the right are accessing the private data from the StopWatch object that was passed in to them. This is allowed because the function (method, member function) is part of the StopWatch class.
| "Normal" use | Functional notation |
|---|---|
|
|
is the same as this:StopWatch sw3 = sw1 + sw2;
not this:// Method is invoked on left object StopWatch sw3 = sw1.operator+(sw2);
// This is NOT how it works. StopWatch sw3 = sw2.operator+(sw1);
It is not random, it is not arbitrary, it is not which ever object the compiler chooses. It is always the left object (with a binary operator). Always.
What about this now?
StopWatch sw1(30); // What about this? StopWatch sw2 = 2 + sw1;
| Non-member function | Member function |
|---|---|
|
|
With the global function, the functional notation looks like this:
which converts to this (using the conversion constructor):StopWatch sw2 = operator+(2, sw1);
But with built-in types, there are no member functions, so it can't even attempt this:StopWatch sw2 = operator+(StopWatch(2), sw1);
This sometimes makes global functions more useful than member functions.StopWatch sw2 = 2.operator+(sw1);
Note: If the left-hand side of an operator is not a user-defined type (e.g it's a built-in type like int or double) then you can't make the function a member function. It must be a global function.
Overloading operator<<
Although the Display method is nice, we'd like to output the C++ way:StopWatch sw1(60); // Works just fine sw1.Display(); // But we'd like to do this std::cout << sw1;
Error message from that line above.
Time for another overload: (This code is almost identical to the code in Display)
std::ostream& operator<<(std::ostream& os, const StopWatch& sw)
{
// Get the seconds from the object
int total_seconds = sw.GetSeconds();
// Calculate hrs/mins/secs
int hours, minutes, seconds;
hours = total_seconds / 3600;
minutes = (total_seconds - (hours * 3600)) / 60;
seconds = total_seconds % 60;
// Print them
os.fill('0');
os << std::setw(2) << hours << ':';
os << std::setw(2) << minutes << ':';
os << std::setw(2) << seconds << std::endl;
// Must return the ostream reference
return os;
}
These are the same now:
// "Infix" notation std::cout << sw1; |
// Functional notation (global function) operator<<(std::cout, sw1); |
What if the GetSeconds method was removed (or made private)? Make the method a member function?StopWatch sw1(60); StopWatch sw2(100); StopWatch sw3(200); // Output: // 00:01:00 // 00:01:40 // 00:03:20 std::cout << sw1 << sw2 << sw3;
This is what the funtional notation looks like for a member function:
See the problem?// Functional notation (member function) std::cout.operator<<(sw1);
We can't make it a member function of the StopWatch class. It would need to be a member of the ostream class, which we cannot modify.
The friend Keyword
Since we can't make the output operator a member of the ostream class, we need a way to allow the overloaded output operator function to access private members of the StopWatch class. We do this by making the overloaded output operator a friend of the StopWatch class:
class StopWatch
{
public:
// Other public stuff ...
// Declare this function a friend so it can access the private members of this class
friend std::ostream& operator<<(std::ostream& os, const StopWatch& rhs);
private:
int seconds_;
};
Implementation
| Global overloaded operator (friend function) | Old member function |
|---|---|
|
|
Why do we return an ostream reference? To support this syntax:
The << operator associates left to right. This is why operator<< must return an ostream object. The compiler really sees the above as:int i, j, k; // Set i, j, k cout << i << j << k << endl;
int i, j, k;
// Set i, j, k
(((cout << i) << j) << k) << endl;
(( cout << j) << k) << endl;
( cout << k) << endl;
cout << endl;
Note:
Just like in the Real World™, friendship is a one-way street: "I may be your friend, but that doesn't make you my friend." Friendship is granted from one entity to another. With operator<<, the StopWatch class is granting friendship to the operator (the operator is NOT granting friendship to the StopWatch class). If you need it to work both ways, then both entities must grant the friendship.
Also, granting friendship is an advanced technique and should be limited to operator<< until you have a very good understanding of C++ class design. In other words, it should be used as a last resort.
Automatic Conversions and User-Defined Types
Recall that a single-argument constructor is called a conversion constructor because it "converts" its argument into an object of the class type.
void f1()
{
StopWatch sw1; // Default constructor
sw1 = 60; // same as sw1 = (StopWatch)60;
// same as sw1 = StopWatch(60);
}
Another example:
| Function expects StopWatch | Call the function |
|---|---|
|
StopWatch sw1(60); int time = 90; // Pass StopWatch fooSW(sw1); // Convert int to StopWatch fooSW(time); |
void f1()
{
StopWatch sw1;
int seconds1 = sw1; // Error, how do you convert StopWatch to int?
int seconds2 = (int) sw1; // Error, how do you convert StopWatch to int?
int seconds3 = int(sw1); // Error, how do you convert StopWatch to int?
}
Microsoft:
GNU:'initializing' : cannot convert from 'StopWatch' to 'int' 'type cast' : cannot convert from 'StopWatch' to 'int'
Q: What to do?error: cannot convert 'StopWatch' to 'int' in initialization error: 'class StopWatch' used where a 'int' was expected
We'll call the function ToInt because it will convert a StopWatch into an int:
| Declaration | Implementation |
|---|---|
|
|
void f2()
{
StopWatch sw1(60);
// Convert to an int explicitly
int seconds = sw1.ToInt();
std::cout << seconds << std::endl;
}
If we want the compiler to perform the conversions (casts) automatically, then
we have to give the function a special name. (Not unlike overloaded operator functions.)
Operator precedence chart for C++
| Declaration | Implementation |
|---|---|
|
|
Sample usage:
void f3()
{
StopWatch sw1(60);
// Convert to an int implicitly
int seconds = sw1;
std::cout << seconds << std::endl;
}
General form of the conversion operator:
Depending on the direction of the conversion:operator type() const
Built-in type ===> Conversion constructor ===> User-defined type
User-defined type ===> Conversion operator ===> Built-in type
For example:
60 ===> Conversion constructor ===> StopWatch(60)
StopWatch(60) ===> Conversion operator ===> 60
int array[10]; StopWatch temp1(60); // Other code... int temp2 = 0; // Other code... int value = array[temp1]; std::cout << value << std::endl; int swval = temp1; std::cout << swval << std::endl; |
Output: 2281060 60 |
We can still convert a StopWatch to an integer explicitly:error: no match for 'operator[]' in 'array[temp1]' error: cannot convert `StopWatch' to `int' in initialization
We can also instruct the compiler not to automatically convert an integer to a StopWatch (conversion constructor):int array[10]; StopWatch temp1(60); // Other code... int temp2 = 0; // Other code... int value = array[temp2]; // correct std::cout << value << std::endl; int swval = temp1.ToInt(); // explicit std::cout << swval << std::endl;
class StopWatch
{
public:
// Constructors
StopWatch();
// Suppress automatic conversions
explicit StopWatch(int seconds);
StopWatch(int hours, int minutes, int seconds);
// Other public methods
private:
int seconds_;
};
Given this function from before:
void fooSW(const StopWatch& sw)
{
// Do something with sw
sw.Display();
}
| Illegal | Legal |
|---|---|
int time = 90; // Error: No implicit conversions fooSW(time); |
int time = 90; // OK: Explicit conversion fooSW(StopWatch(time)); |
For example, the StopWatch class can make the conversion function explicit:C++11 has added the ability to mark conversion functions explicit (like conversion constructors). This means you can use the C++-style of explicit casting without having the problem of the compiler doing it unintentionally.
Sample usage:// Conversion function/operator (explicit) explicit operator int() const;
StopWatch sw1(60); int seconds = sw1; // Error: Can't convert to an int implicitly int seconds = int(sw1); // OK: Explicit conversion
Overloading Operators with Side-effects
Up to this point:| Using integers | Using StopWatches |
|---|---|
|
|
| Using integers | Using StopWatches |
|---|---|
|
|
| Declarations | Implementations |
|---|---|
|
|
| Using integers | Using StopWatches |
|---|---|
|
|
Challenge Question: How will you overload the pre/post increment operators to support this code?
void foo()
{
StopWatch sw1(60);
// Pre increment
// afterwards sw2 == sw1
StopWatch sw2 = ++sw1;
std::cout << sw1; // 00:01:01
std::cout << sw2; // 00:01:01
// Post increment
// afterwards sw2 != sw1
sw2 = sw1++;
std::cout << sw1; // 00:01:02
std::cout << sw2; // 00:01:01
// Fails to compile. Same as this: ++(sw1++)
sw2 = ++sw1++;
// Pre and post increment
// afterwards sw2 != sw1
sw2 = (++sw1)++;
std::cout << sw1; // 00:01:04
std::cout << sw2; // 00:01:03
}
Operator precedence chart for C++ will help you understand why one of the expressions fails to compile.
Member, Friend, or Non-member?
In most cases, you have three choices when overloading operators: 1) member, 2) friend, 3) non-member/non-friend (global or in a namespace)
StopWatch StopWatch::operator*(int factor) const
{
StopWatch sum(seconds_ * factor);
return sum;
}
// Declaration (in class StopWatch) requires the friend keyword.
friend StopWatch operator*(int factor, const StopWatch &sw);
// Definition (outside of class StopWatch) must NOT have the friend keyword.
StopWatch operator*(int factor, const StopWatch &sw)
{
StopWatch sum(sw.seconds_ * factor);
return sum;
}
StopWatch operator*(int factor, const StopWatch &sw)
{
StopWatch sum(sw.GetSeconds() * factor);
return sum;
}
StopWatch operator*(int factor, const StopWatch &sw)
{
// the compiler sees it like this: sw.operator*(factor)
return sw * factor;
}
Study these four functions above and be sure you understand exactly how each one is different and why. Also make sure you know why some functions have 1 parameter and some have 2 parameters. Unary operators will have 0 or 1 parameter(s), depending on whether they are members or not.
Restrictions on Operator Overloading
// Compiler error: Cannot overload built-in operators for int int operator+(int left, int right);
This also means that you can't change the precedence or associativity of an operator. (What a nightmare that would be!)int i; StopWatch sw; % i; // % requires 2 integer operands % sw; // if overloaded for StopWatch, % would require 2 operands
:: Scope resolution operator .* Pointer-to-member operator . Membership operator ?: Conditional operator sizeof The sizeof operator typeid A RTTI operator const_cast A type cast operator dynamic_cast A type cast operator reinterpret_cast A type cast operator static_cast A type cast operator
= Assignment operator () Function call operator [] Subscripting operator -> Class member access by pointer operator
+ - * / % ^ & | ~= ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () [] new delete new [] delete []
Member Initialization List
A simple Point class that uses the constructor to give values to the private data:
| Declaration | Implementation |
|---|---|
|
|
Point::Point(int x, int y) : x_(x), y_(y) // initialization
{
}
The result for this example is the same as the first constructor above.
Look at the class below. Can you see what is wrong with the constructor? (The compiler will give errors.)
| Class definition | Constructor implementation |
|---|---|
|
|
void f20()
{
int a = 1, b = 2, c = 3;
Foo foo(a, b, c); // 1,2,3
}
Constants and references must be initialized via the initializer list (otherwise, it's an assignment, which is illegal). The references notes.error: uninitialized member 'Foo::y_' with 'const' type 'const int' error: uninitialized reference member 'Foo::z_' error: assignment of read-only data-member 'Foo::y_'
Foo::Foo(int x, int y, int& z) : y_(y), z_(z) // initialization
{
x_ = x; // assignment
}
Some people prefer to initialize all members in the list:
Foo::Foo(int x, int y, int& z) : y_(y), z_(z), x_(x) // initialization
{
}
Note that g++ will give you a warning with the above code:
The proper way:warning: `Foo::z_' will be initialized after warning: `int Foo::x_' warning: when initialized here
Foo::Foo(int x, int y, int& z) : x_(x), y_(y), z_(z)
{
}
Notes:
Foo::Foo(int x, int y, int& z) : x_(x), z_(z), y_(y * z_)
{
std::cout << x_ << std::endl;
std::cout << y_ << std::endl;
std::cout << z_ << std::endl;
}
Output:
1 4561296 3
// The third parameter, z, is no longer a reference
Foo::Foo(int x, int y, int z) : x_(x), y_(y), z_(z)
{
}
Creating a Foo object and displaying the values:
void f20()
{
int a = 1, b = 2, c = 3;
Foo foo(a, b, c);
std::cout << foo.x_ << std::endl;
std::cout << foo.y_ << std::endl;
std::cout << foo.z_ << std::endl;
}
Output:
The problem is that we are binding z_ to a temporary, stack-based object. The third parameter, z, is passed by value (i.e. a temporary copy). That means that when we bind to the copy, we are binding to an address on the stack. When the constructor returns, that stack location is over-written by other code yet z_ is still referring to that location. The problem isn't just because we are binding to a stack address, the problem is that the reference, z_, will outlive the temporary on the stack.1 2 32516
Remember that a reference is really just an alias, meaning, a reference is just another name for an existing object (address). In the case above, the reference is just another name for the area of memory on the stack (that will be overwritten when the function returns).
What makes this bug worse is that most compilers won't detect this problem! However, the Clang compiler (clang++) will detect it and say this:
main-overload-members.cpp:47:50: warning: binding reference member 'z_' to stack allocated parameter 'z' [-Wdangling-field]
Foo::Foo(int x, int y, int z) : x_(x), y_(y), z_(z)
^
main-overload-members.cpp:27:10: note: reference member declared here
int& z_;
^
1 warning generated.
Clang says it's just a warning, not an error, so if you are not careful, you could end up
with an executable that, when run, will exhibit undefined behavior.
Finally, to make it even worse, sometimes you will actually get the correct output! This means that it may work on your computer, but it won't work on others. These are bugs that are very hard to find (if the compiler doesn't detect the problem).
Using -Weffc++ (from the g++ man page)