Chapter 17 - Operator
Overloading
Chapter Goals
- To learn about operator overloading
- To learn the various categories of operators and their uses
- To learn how operator overloading is employed in the standard library
- To be able to implement operators in your own classes
- To learn how to avoid common errors in operator overloading
17.1 Operator Overloading
- Convenient shorthand
- More intuitive:
a + b * c
vs.
plus(a, times(b, c))
- C++ supports a rich set of operators
17.1 Operator Overloading
- Programmer may define his/her own
- Powerful and subtle feature of C++
- Advantages:
- Easier to remember
- Reuse of existing code
- Concise description of the task
- Caveat:
- Lack of context may disguise meaning
17.1 Operator Overloading
To define an operator:
- Can't redefine existing behavior
- Can't change precedence, associativity, or arity
- Can't invent new symbols
|
Overloadable Operators
|
| + |
- |
* |
/ |
% |
^ |
& |
| | |
~ |
! |
= |
< |
> |
+= |
| -= |
*= |
/= |
%= |
^= |
&= |
|= |
| << |
>> |
<<= |
>>= |
== |
!= |
<= |
| >= |
&& |
|| |
++ |
-- |
->* |
. |
| -> |
[] |
() |
new |
new[] |
delete |
delete[] |
17.1 Operator Functions
- Can be:
- Simple (non-member) functions
- Member functions
- To name function: precede the operator symbol with operator
17.1 Operator Functions
(cont.)
Syntax 17.2 :
Overloading Operator Definition
return_type operatoroperator_symbol( parameters )
{
statements
}
|
Example: |
int operator-( Time a, Time b )
{
return a.seconds_from( b )
}
|
| Purpose: |
Supply the implementation of an overloaded
operator. |
|
17.1 Example - Operator
Functions
- The difference between two Time objects is the
number of seconds between them
int operator-( Time a, Time b )
{
return a.seconds_from( b );
}
- Use the - operator instead of calling seconds_from():
Time now;
Time morning( 9, 0, 0 );
int seconds_elapsed = now - morning;
- operator- is a nonmember function with two parameters
17.1 Operator Overloading
Avoid ambiguous behavior:
some_return_type operator+(Time a, Time b);
- Time objects represent a point in time, not a
duration
- what does 3 P.M. + 3 P.M. mean?
17.1 Operator Overloading (cont.)
- Adding seconds to a Time does make sense:
Time operator+(Time a, int sec)
{
Time r = a;
r.add_seconds(sec);
return r;
}
Caveat: Units are not implied by the context. Add minutes? Seconds?
- A function named add_seconds is clear
17.1 Operator Member
Functions
Syntax 17.2 :
Overloading Operator Member Function Definition
return_type ClassName::operatoroperator_symbol( parameters )
{
statements
}
|
Example: |
Time Time::operator+( int sec )
{
Time r = *this;
r.add_seconds( sec );
return r;
}
|
| Purpose: |
Supply the implementation of an overloaded
operator member function. |
|
17.1 Operator Member
Functions
- First interpreted as a member function of the left-most operand
- Subsequent arguments are passed to the member function:
Time later = now + 60;
becomes
Time later = now.operator+( 60 );
- E.g., a binary operator has only one argument
17.1 Example - Operator Member
Functions
The addition operator as a member function of the Time class:
class Time
{
...
Time operator+( int sec ) const;
};
Time Time::operator+( int sec ) const
{
Time r = *this; // Copy the implicit parameter
r.add_seconds( sec );
return r;
}
17.1 Operator Overloading - member vs.
global
- Some operators (e.g., assignment) are required to be members
- Usually programmer has the choice
- Keep in mind:
- Non-members have only public access (or, make it a friend)
- No implicit conversion for implicit parameters
- Member function is preferable if
- The left argument is modified (+=)
- The data fields are not easily accessible
17.2 Case Study: Fractional
Numbers
New type to represent a ratio of 2 integers:
Fraction a( 3, 4 ); // Represents 3/4
Fraction b( 7 ); // Represents 7/1
Fractions should behave as other numbers:
Fraction c( 1, 2 );
if( a < b )
c = b - a;
else
c = a - b;
cout << "Value is " << c << "\n";
17.2 Case Study: Fractional
Numbers
Should be able to mix w/other numbers:
c = a + 3; // Should mean same as addition of 3/1
double x = 2.5 * a; // Should convert fraction to a double
17.2 Case Study: Fractional Numbers -
Construction
- normalize (fraction.h, line 70):
- fraction is in lowest terms
- denominator is not negative
- Default constructor (line 14) sets value to 0
- Constructor (line 20) sets value to t/1
- Constructor (line 27) sets value to t/b, calls normalize
17.2 Case Study: Fractional Numbers
(fraction.h)
17.2 Case Study: Fractional Numbers
(fraction.cpp)
(Discussion follows)
17.2 Case Study: Fractional Numbers
(fractiontest.cpp)
17.3 Overloading Simple Arithmetic
Operators
- = + - * / %
- Parameters types need not match
Time Time::operator+( int seconds ) const;
- Supply behavior that is analogous to the arithmetic operators;
intuitive
- E.g. (fraction.cpp, lines 61-67)
(See next slide)
17.3 Simple Arithmetic Operators - Fraction
Example
a/b + c/d is defined to be (a*d + c*d)/(b*d)
Fraction operator+(const Fraction& left, const Fraction& right)
{
Fraction result(left.numerator() * right.denominator()
+ right.numerator() * left.denominator(),
left.denominator() * right.denominator());
return result;
}
The remaining operations are defined similarly
17.3 Unary Arithmetic Operators
- + - * & have both binary and unary forms
- To overload unary form, reduce the number of parameters by one
- E.g.:
Fraction operator-(const Fraction& value)
{
Fraction result(-value.numerator(), value.denominator());
return result;
}
- A unary operator defined as a member function takes no arguments
(sect. 17.6)
17.4 Overloading Comparison Operators
- Similar to the arithmetic operators
- Not supplied automatically
- Typically return a bool
- E.g.: Times are equal <=> difference is 0
bool operator==(const Time& a, const Time& b)
{
return a.seconds_from(b) == 0;
}
17.4 Overloading Comparison Operators
- E.g., from the Fraction class:
bool operator==(const Fraction& left, const Fraction& right)
{
return left.numerator() * right.denominator()
== right.numerator() * left.denominator();
}
bool operator<(const Fraction& left, const Fraction& right)
{
return left.numerator() * right.denominator()
< right.numerator() * left.denominator();
}
Productivity Hint 17.2
Define Comparisons in Terms of Each Other
- Write 1 or 2 member functions; equality, and strict inequality
- Define all comparisons in terms of these functions
- E.g.: Fraction class (fraction.cpp)
- A single compare method (line 97), returns an
int
- All 6 comparison operators (lines 104-132) call compare
Advanced Topic 17.2
Symmetry and Conversion
- Arithmetic and comparison operators are generally nonmember
functions
- No implicit conversions of left operand for member functions:
Consider Fraction a;
- ( a == 2 ) works fine (given the constructors)
- ( 2 == a ) will not work if equality is a member
function.
17.5 Overloading Input >> and
Output <<
- Originally shift operators
- Almost always overloaded for I/O
- Overloaded exactly as the binary arithmetic operators
- Should be non-member functions
17.5.1 Stream Output <<
17.5.2 Stream Input >>
- Stream library provides for all built-in types
- Can be overloaded, much like the output operator
- 2nd parameter is a non-const reference
- E.g.: Input operator for Time
17.5.2 Stream Input >> (cont.)
Assume a Time is input as 3 separate integers,
9 15 00 to represent 9:15:00 a.m.
istream& operator>>(istream& in, Time& a)
{
int hours;
int minutes;
int seconds;
in >> hours >> minutes >> seconds;
a = Time(hours, minutes, seconds);
return in;
}
Advanced Topic 17.3
Peeking at the Input
You may put a single character back into the stream.
E.g.: We want to allow a user to input a Fraction as
a single integer (7), or as an integer, slash, integer (3/4)
Advanced Topic 17.3 (cont.)
istream& operator>>(istream& in, Fraction& r)
{
int t, b;
// Read the top
in >> t;
// If there is a slash, read the next number
char c;
in >> c;
if (c == '/')
in >> b;
else
{
in.putback(c);
b = 1;
}
r = Fraction(t, b);
return in;
}
17.6 Overloading Increment
and Decrement Operators
- Generally defined as member functions
- Two versions of each:
- prefix version: ++x
- postfix version: x++
- C++ uses an extra argument to distinguish the postfix form
17.6 Overloading Increment
and Decrement Operators
- E.g.: Increments for Fraction
Notice:
- Prefix returns a reference
- Postfix does more work
class Fraction
{
. . .
Fraction& operator++(); // Prefix form
Fraction operator++(int unused); // Postfix form
. . .
};
17.6 Overloading Increment
and Decrement Operators (cont.)
Fraction& Fraction::operator++()
{
top += bottom;
return *this;
}
Fraction Fraction::operator++(int unused)
{
Fraction clone(top, bottom);
top += bottom;
return clone;
}
17.6.1 Iterators and Overloaded Operators
- Iterators (Sect. 16.5) can be used just like pointers (Chptr. 10) to
process arrays
- Iterators can be used (in varying capacities) on all standard
containers
- Operators overloaded for iterators:
| + | addition |
| == | comparison |
| * | dereference |
| ++ | increment (post and pre) |
17.6.1 Example: Implementing Iterator Operators
Recall Iterator, defined on our List of strings. We can
now overload the standard operators:
Iterator& Iterator::operator++(int)
{
position = position->next;
return *this;
}
string Iterator::operator*() const
{
assert(position != NULL);
return position->data;
}
17.6.1 Example: Implementing Iterator Operators
(cont.)
bool Iterator::operator==(const Iterator& b) const
{
return position == b.position;
}
bool Iterator::operator!=(const Iterator& b) const
{
return position != b.position;
}
17.7 Overloading the Assignment
Operators
The Assignment Operator,
=
- Must be a member function
- Is automatically generated
- Member-wise copy
- Destructor and copy constructor also supplied by compiler
- Desired behavior for many classes
- Explicitly supply if class has outside references
17.7.1 Overloading Compound Assignment
Operators (+= *=, etc.)
17.8 Overloading Conversion Operators
17.8 Overloading Conversion Operators
17.8 Overloading Conversion Operators
- Convert from a user-defined type
- An operator must be supplied for each type to convert to
- Definition:
- Must be a member function
- Has a type as its name
- Has no return type
- Takes no arguments
- May be used implicitly, or called explicitly (cast)
Note: The compiler
performs at most one level of user-defined type conversion when matching an
overloaded function.
17.8 Example - Overloading Conversion
Operators
Convert a Fraction to a double:
Fraction::operator double() const
{
// Convert numerator to double, then do division
return static_cast<double>(top) / bottom;
}
Can be used:
Fraction a(1, 2);
double d = 7.0;
double halfd = d * a; // a is converted to double to do multiplication
cout << "one half seven is " << halfd << "\n";
17.8.1 Stream Loops and Conversion
Operators
Consider the familiar construct:
while( cin >> x )
. . .
- The >> operator returns istream&
- Not a valid boolean expression
- istream provides a conversion operator that returns
false on end of input or on error
17.9 Overloading the Subscript
Operator
- Often defined on an indexable container (vector or
map)
- Explicit argument is the index
- Result is the value stored at the given position
- Return a reference to the value to get an L-value
- Must be a member function
- operator[][] does not exist
17.9 Example: Overloading the Subscript
Operator
Implementation of a "safe array":
- The subscript operator checks that index is valid
- First (non-const) version returns a reference, an L-value
- The second version is const, returns a copy
17.9 Example: Overloading the Subscript
Operator (cont.)
class SafeArray
{
public:
SafeArray(int s);
SafeArray(const int v[], int s);
int& operator[](int i);
int operator[](int i) const;
private:
int size;
int* values;
};
SafeArray::SafeArray(int s) : size(s), values(new int[size]) {}
17.9 Example: Overloading the Subscript
Operator (cont.)
SafeArray::SafeArray(const int v[], int s) : size(s)
{
values = new int[size];
for (int i = 0; i < size; i++)
values[i] = v[i];
}
int& SafeArray::operator[](int index)
{
assert((index >= 0) && (index < size));
return values[i];
}
int SafeArray::operator[](int index) const;
{
assert((index >= 0) && (index < size));
return values[i];
}
17.10 Overloading the Function Call
Operator
- Objects can be used as functions (function object)
- Must be defined as a member function
- Can carry (and change) its own data values
- Only operator where the number of arguments is not
fixed
- Can be passed where template functions can't
- Used extensively by various generic algorithms in the STL (chptr. 24)
17.10 Example: The Function Call
Operator
- Simple to write a function that takes no arguments and returns a random
number on [1, 100]
- More difficult to write a similar function (no args) that returns
a value on [a, b], where a and b aren't known until run time
- A function object makes it simple:
17.10 Example: The Function Call
Operator
class RandomInt
{
public:
RandomInt(int ia, int ib);
int operator()();
private:
int a, b;
};
RandomInt::RandomInt(int ia, int ib) : a(ia), b(ib) {}
int RandomInt::operator()()
{
return a + rand() % (b - a + 1);
}
17.10 Example: The Function Call
Operator
Declare an instance, then use it as a function:
RandomInt a(7, 15); // Return random values from 7 to 15
cout << "one random value " << a() << "\n";
cout << "and another " << a() << "\n";
17.10 Example 2: Variable arguments
class RandomInt
{
public:
RandomInt(int ia, int ib);
int operator()();
int operator()(int nb);
int operator()(int na, nb);
private:
int a, b;
};
17.10 Example 2: Variable arguments (cont.)
RandomInt::RandomInt(int ia, int ib) : a(ia), b(ib) {}
int RandomInt::operator()()
{ return a + rand() % (b - a + 1); }
int RandomInt::operator()(int nb)
{ return a + rand() % (nb - a + 1); }
int RandomInt::operator()(int na, int nb)
{ return na + rand() % (nb - na + 1); }
17.10 Example 2: Variable arguments
The function selected will be determined by the number of arguments provided:
RandomInt r(3, 7);
cout << "random value between 3 and 7 " << r() << "\n";
cout << "random value between 3 and 10 " << r(10) << "\n";
cout << "random value between 23 and 30 " << r(23, 30) << "\n";
Advanced Topic 17.5
Other Operators
Operators not commonly used, hence, not commonly overloaded:
- Comma operator
- Has lower precedence than assignment
- &, | and ^
- ^ has lower precedence than addition.
Advanced Topic 17.5
Other Operators (cont.)
- && and ||
- Lose their short-circuit logic when overloaded
- ~ and !
- ->
- new and delete
Advanced Topic 17.6
Inline Functions
- Avoid overhead of function calls
- Body expanded in place of each call
- Only for very short functions ( <= 3 lines )
To define:
- Precede definition w/ inline
- Place in interface file
- Same for member function
- Alternatively, define member function inside class definition
17.11 Case Study: Matrices
Operations on matrices:
- Addition
- Scalar multiplication
- Matrix multiplication
- Single row by single column
- Apply above algorithm to all rows of first by all columns in
second
17.11 Case Study: Matrices
Implementation:
- 2D array:
- Indices not checked
- Need helper functions to copy
- Can't use standard arithmetic operators
- Create Matrix:
- Number of ROWS and COLUMNS variable; can't use a 2D array
underneath
- Use a 1D array. So, elementi,j will be
found at
i * COLUMNS + j
17.11 Case Study: Matrices (cont.)
- Subscripting:
- Overload function-call operator, OR
- Subscript ([]) returns MatrixRow, which
provides its own ([]) operator
- We choose option 2
- const vs. non-const => also provide a
ConstMatrixRow
- Small functions will be inline
17.11 Case Study: Matrices
(matrix1.h)
17.11 Case Study: Matrices
(matrix1.cpp)
Implementation for the Matrix class:
17.11 Case Study: Matrices
(matrixtest1.cpp)
Chapter Summary
- Define new meanings for C++ operators
- Member vs. nonmember operators
- Extend new types using stream I/O operators
- Simulate pointers by defining operators on iterators
- Operators to define behavior of conversions
- Function objects