"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
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.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
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 |
---|---|
class StopWatch { public: // Default constructor StopWatch(); // Conversion constructor StopWatch(int seconds); // Non-default constructor StopWatch(int hours, int minutes, int seconds); void Increment(int seconds = 1); void Reset(); int GetSeconds() const; void Display() const; private: int seconds_; }; |
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:
and call it: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; }
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:
Compare this to our original "C-style" function: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; }
This makes life much simpler for the users of the StopWatch class.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; }
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:Test it: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; }
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; }
Now, this multiply function will simply call the first multiply function.// Multiply: int * StopWatch StopWatch operator*(int lhs, const StopWatch& rhs) { // Simply reverse the operands return rhs * lhs; }
How about adding an integer to a StopWatch:
Current implementation (add 2 Stopwatches) | New expected behavior |
---|---|
StopWatch operator+(const StopWatch& lhs, const StopWatch& rhs) { // lhs is a StopWatch // rhs is a StopWatch StopWatch sw(lhs.GetSeconds() + rhs.GetSeconds()); // Return the result return sw; } |
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'
Question: Given these functions below:binary '+' : 'StopWatch' does not define this operator or a conversion to a type acceptable to the predefined operator
will the compiler accept the code below? (Why or why not?)// 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; }
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:If this method wasn't available, none of this would work. One solution would be to make member functions: (in StopWatch.h)StopWatch operator+(const StopWatch& lhs, const StopWatch& rhs) { // Calls public gettor method StopWatch sw(lhs.GetSeconds() + rhs.GetSeconds()); return sw; }
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)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_; };
Implementations: (in StopWatch.cpp)
Original global functions | New member functions |
---|---|
StopWatch operator+(const StopWatch& lhs, const StopWatch& rhs) { // Add the two seconds StopWatch sw(lhs.GetSeconds() + rhs.GetSeconds()); return sw; } StopWatch operator-(const StopWatch& lhs, const StopWatch& rhs) { // Subtract from this object's seconds_ // (Maybe check for negative first?) StopWatch sw(lhs.GetSeconds() - rhs.GetSeconds()); return sw; } StopWatch operator*(const StopWatch& lhs, int rhs) { // Multiply lhs seconds_ by rhs StopWatch sw(lhs.GetSeconds() * rhs); return sw; } |
StopWatch StopWatch::operator+(const StopWatch& rhs) const { // Add seconds to this object's seconds_ StopWatch sw(seconds_ + rhs.seconds_); return sw; } StopWatch StopWatch::operator-(const StopWatch& rhs) const { // Subtract from this object's seconds_ // (Maybe check for negative first?) StopWatch sw(seconds_ - rhs.seconds_); return sw; } StopWatch StopWatch::operator*(int rhs) const { // Multiply this object's seconds_ by rhs StopWatch sw(seconds_ * rhs); return sw; } |
// 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 |
---|---|
void f1() { StopWatch sw1(30); StopWatch sw2(10); StopWatch sw3 = sw1 + sw2; StopWatch sw4 = sw1 + 20; StopWatch sw5 = sw1 - sw2; StopWatch sw6 = sw1 - 20; StopWatch sw7 = sw1 * 10; sw3.Display(); // 00:00:40; sw4.Display(); // 00:00:50; sw5.Display(); // 00:00:20; sw6.Display(); // 00:00:10; sw7.Display(); // 00:05:00; } |
void f2() { StopWatch sw1(30); StopWatch sw2(10); // Functional notation StopWatch sw3 = sw1.operator+(sw2); sw3.Display(); // 00:00:40 // Functional notation StopWatch sw4 = sw1.operator+(20); sw4.Display(); // 00:00:50 // Functional notation StopWatch sw7 = sw1.operator*(10); sw5.Display(); // 00:05:00 } |
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 |
---|---|
StopWatch operator+(const StopWatch& lhs, const StopWatch& rhs) { StopWatch sw(lhs.GetSeconds() + rhs.GetSeconds()); return sw; } |
StopWatch StopWatch::operator+(const StopWatch& rhs) const { // Add seconds to this object's seconds StopWatch sw(seconds_ + rhs.seconds_); return sw; } |
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)
These are the same now: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; }
// "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:Implementationclass 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_; };
Global overloaded operator (friend function) | Old member function |
---|---|
std::ostream& operator<<(std::ostream& os, const StopWatch& sw) { int hours, minutes, seconds; // A friend can access private members of sw hours = sw.seconds_ / 3600; minutes = (sw.seconds_ - (hours * 3600)) / 60; seconds = sw.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 a reference to the ostream return os; } |
void StopWatch::Display() const { int hours, minutes, seconds; // Can access private members of this object hours = seconds_ / 3600; minutes = (seconds_ - (hours * 3600)) / 60; seconds = seconds_ % 60; // Print them std::cout.fill('0'); std::cout << std::setw(2) << hours << ':'; std::cout << std::setw(2) << minutes << ':'; std::cout << std::setw(2) << seconds << std::endl; } |
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;
Note:int i, j, k; // Set i, j, k (((cout << i) << j) << k) << endl; (( cout << j) << k) << endl; ( cout << k) << endl; cout << endl;
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.
Another example:void f1() { StopWatch sw1; // Default constructor sw1 = 60; // same as sw1 = (StopWatch)60; // same as sw1 = StopWatch(60); }
Function expects StopWatch | Call the function |
---|---|
void fooSW(const StopWatch& sw) { // Do something with sw sw.Display(); } |
StopWatch sw1(60); int time = 90; // Pass StopWatch fooSW(sw1); // Convert int to StopWatch fooSW(time); |
Microsoft: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? }
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 |
---|---|
class StopWatch { public: // Other public methods ... // Convert to int (explicit) int ToInt() const; private: int seconds_; }; |
int StopWatch::ToInt() const { return seconds_; } |
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.)void f2() { StopWatch sw1(60); // Convert to an int explicitly int seconds = sw1.ToInt(); std::cout << seconds << std::endl; }
Operator precedence chart for C++
Declaration | Implementation |
---|---|
class StopWatch { public: // Other public methods // Conversion function/operator (implicit) operator int() const; private: int seconds_; }; |
StopWatch::operator int() const { return seconds_; } |
Sample usage:
General form of the conversion operator:void f3() { StopWatch sw1(60); // Convert to an int implicitly int seconds = sw1; std::cout << seconds << std::endl; }
Depending on the direction of the conversion:operator type() const
For example:Built-in type ===> Conversion constructor ===> User-defined type User-defined type ===> Conversion operator ===> Built-in type
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 |
---|---|
void f6() { int a = 1; int b = 2; int c; // New value stored in c. // a and b are unchanged. c = a + b; } |
void f5() { StopWatch sw1(30); StopWatch sw2(60); // New object is created // sw1 and sw2 unchanged StopWatch sw3 = sw1 + sw2; } |
Using integers | Using StopWatches |
---|---|
void f7() { int a = 5; int b = 7; // a is changed // b is unchanged a += b; } |
void f8() { StopWatch sw1(30); StopWatch sw2(60); // sw1 is changed // sw2 is unchanged sw1 += sw2; } |
Declarations | Implementations |
---|---|
class StopWatch { public: // Overload for: sw1 + sw2 StopWatch operator+(const StopWatch& rhs) const; // Overload for: sw1 += sw2 StopWatch& operator+=(const StopWatch& rhs); // Other public methods ... private: int seconds_; }; |
StopWatch StopWatch::operator+(const StopWatch& rhs) const { // Create a new object from both operands StopWatch sw(seconds_ + rhs.seconds_); return sw; } StopWatch& StopWatch::operator+=(const StopWatch& rhs) { // Modify this object directly seconds_ += rhs.seconds_; return *this; } |
Using integers | Using StopWatches |
---|---|
void f9() { int a = 1; int b = 2; int c = 3; // Associates right to left // Changes a and b a += b += c; std::cout << a << std::endl; // 6 std::cout << b << std::endl; // 5 std::cout << c << std::endl; // 3 } |
void f10() { StopWatch sw1(30); StopWatch sw2(60); StopWatch sw3(90); // Associates right to left // Changes sw1 and sw2 sw1 += sw2 += sw3; std::cout << sw1; // 00:03:00 std::cout << sw2; // 00:02:30 std::cout << sw3; // 00:01:30 } |
Challenge Question: How will you overload the pre/post increment operators to support this code?
Operator precedence chart for C++ will help you understand why one of the expressions fails to compile.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 }
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 |
---|---|
class Point { public: Point(int x = 0, int y = 0); private: int x_; int y_; }; |
Point::Point(int x, int y) { x_ = x; // assignment y_ = y; // assignment } |
The result for this example is the same as the first constructor above.Point::Point(int x, int y) : x_(x), y_(y) // initialization { }
Look at the class below. Can you see what is wrong with the constructor? (The compiler will give errors.)
Class definition | Constructor implementation |
---|---|
class Foo { public: Foo(int x, int y, int& z); private: int x_; const int y_; int& z_; }; |
Foo::Foo(int x, int y, int& z) { x_ = x; // assignment y_ = y; // assignment z_ = z; // assignment } |
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_'
Some people prefer to initialize all members in the list:Foo::Foo(int x, int y, int& z) : y_(y), z_(z) // initialization { x_ = x; // assignment }
Note that g++ will give you a warning with the above code:Foo::Foo(int x, int y, int& z) : y_(y), z_(z), x_(x) // initialization { }
The proper way:warning: `Foo::z_' will be initialized after warning: `int Foo::x_' warning: when initialized here
Notes:Foo::Foo(int x, int y, int& z) : x_(x), y_(y), z_(z) { }
Output: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; }
1 4561296 3
Creating a Foo object and displaying the values:// The third parameter, z, is no longer a reference Foo::Foo(int x, int y, int z) : x_(x), y_(y), z_(z) { }
Output: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; }
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: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.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.
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)