Chapter 10 - Operator Overloading - Part 1
Chapter 10 - Operator Overloading - Part 1
Kristina Shroyer
1
Chapter Objectives
l What operator overloading is and how it makes programs more
readable and programming more convenient.
2
Introduction to Operator Overloading
l We've been sending messages between instances of our classes using the
dot operator
n This can become cumbersome for certain operations in certain kinds of classes
(ones with mathematical operations for example)
n Sometimes it would be easier and more clear if we could use operators to perform
some of the methods/operations on the data members of our class data types
3
Introduction to Operator Overloading
l We've been using operator overloading implicitly since we first began
programming
l Examples
<<
♦ Stream insertion, bitwise left-shift
+
♦ Performs arithmetic on multiple items (integers, floats, etc.), when used with strings
concatenates them, performs differently when used with pointers
♦ So the + behaves differently depending on what its operands are, we can also define how
the + should behave on instances of our data types
l There are certain situations where it's appropriate to overload operators and
certain situations where it is not appropriate, we'll learn both
4
Fundamentals of Operator Overloading
l Programmers can use operators with user defined (ie class) data types
(operator overloading)
n New operators can NOT be created
n But existing operators can be overloaded so that when these operators are used with
objects they have meaning appropriate to those objects
♦ Similar to how + means one thing when used on strings and other thing when used on
numeric types
5
Fundamentals of Operator Overloading
l To use an operator on a class object (instance), it must be overloaded for
that class data type
l There are THREE Exceptions: (these can also optionally be overloaded by the
programmer)
1. Assignment operator (=)
l Memberwise assignment between objects (this is how it works if NOT overloaded by the programmer)
l Can be overloaded by the programmer
l As we've seen this operator (or anything involving memberwise assignment such as the copy
constructor) doesn't work well with pointers and should be overloaded in these type of cases
2. Address operator (&)
l Returns address of object
l Can be overloaded by the programmer
3. Comma operator (,)
l Evaluates expression to its left then the expression to its right
l Returns the value of the expression to its right
l Can be overloaded by the programmer
6
Fundamentals of Operator Overloading
l Programming Observations and Tips
n Operator overloading contributes to C++’s extensibility
n Use operator overloading when it makes a program clearer
than accomplishing the same operations with function calls
n Overloaded operators should mimic the functionality of their
built-in counterparts
♦ Example: the + operator should be overloaded to perform
addition, not subtraction
♦ Avoid excessive or inconsistent use of operator overloading,
as this can make a program cryptic and difficult to read.
7
Restrictions on Operator Overloading
l Most of C++'s operators can be overloaded but
there are some which cannot
n The operators that can and can not be overloaded are
shown below
+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
-- ->* , -> [] () new delete
new[] delete[]
. .* :: ?:
8
Restrictions on Operator Overloading
l Certain things cannot be changed by
operator overloading
l Operator Overloading Can NOT change the following:
1. Precedence of operator (order of evaluation)
♦ Use parentheses to force order of operators
2. Associativity (left-to-right or right-to-left)
3. Number of operands (this is called the arity of an operator)
♦ e.g., & is unary, can only act on one operand
♦ C++ has one ternary operator ?: which cannot be overloaded
4. How operators act on built-in (fundamental) data types (i.e.,
cannot change integer addition)
♦ Operator overloading only works with objects of user defined
types - or - with a mixture of an object of a user defined type
and an object of a fundamental (primitive/built in) type
♦ So operator overloading will never work with only
fundamental types
9
Restrictions on Operator Overloading
l No new operators allowed (ie cannot create new
operators)
n Only existing operators can be overloaded
n Unfortunately this prevents using notations like ** for
exponentiation
♦ you could overload an existing operator for exponentiation
though
10
Restrictions on Operator Overloading
l Common Programming Errors/Restriction Summary
n Attempting to overload a non-overloadable operator is a syntax error
n Attempting to change the arity (how many operands it takes) of an
operator via operator overloading is a compilation error
n Attempting to create new operators via operator overloading is a
syntax error (like **)
n At least one argument of an operator function must be an object or
reference of a user-defined type
♦ This prevents programmers from changing how operators work on
fundamental types
♦ Attempting to modify how an operator works with objects of fundamental
types is a compilation error
n Assuming that overloading an operator such as + overloads related
operators such as += or that overloading == overloads a related
operator like != can lead to errors
♦ Operators can be overloaded only explicitly; there is no implicit overloading
11
Operator Functions as Class Member Functions vs.
Global Functions
l Operator overloading functions can be:
1. member functions
n Use the this pointer implicitly to obtain one of their class object arguments (the left operand for binary
operators) (r3 = r1 + r2 calls r1.operator+(r2) – uses r1's this pointer in the function)
n Remember one of the operands must be an object, can't override an operator on two primitive
(fundamental) types
2. global functions
♦ There are certain cases as we'll see when we have to do operator overloading with global functions
♦ global functions are often made friend functions for performance reasons (this is what I was talking
about in Chapter 9 – the one case I know where friend functions are needed)
l This also gives them direct access to private or protected member data
♦ arguments for both operands must be explicitly listed
12
Operator Functions as Class Members vs. Global Members
l Should the operator be overloaded as a global function or a member
function?
n In the cases where there is a choice an operator is still used in the same way in expressions
whether it is overloaded as a global function or a member function, so which should be used?
2. This means operator functions overloaded as member functions are implicitly called when
l Left operand of binary operator is of this class
l Single operand of unary operator is of this class
13
Operator Functions as Class Members vs. Global Members
l The stream insertion (<<) and stream extraction (>>) operators must be overloaded
as global functions
l This means the << and >> operators can NOT be overloaded as member functions
since the left operands are not objects of the same type as the class the overloaded
operator function is defined in
n So in your Rectangle class if you want to overload << you'll want to be able to do this:
♦ cout << r1; //where r1 is of type Rectangle
♦ Since cout is of type ostream& and not of type Rectangle the << can't be overloaded as a member function
– can NOT call cout.operator<<(r1) if << can only operate on Rectangles – this doesn't make sense
l Since each of these overloaded operator functions may/most likely will require access to
the private members of the object being output or input, these overloaded functions
can be (and often are) made friend functions
14
Operator Functions as Class Members vs. Global Members
15
Operator Functions as Class Members vs. Global Members
l Commutative operators
n These are a another reason you may want/need to make an operator
overloading function a global function instead of a member function
l Example:
n May want + to be commutative for the HugeInt class
♦ So both “a + b” and “b + a” work
♦ Overloaded operator can only be member function when its class is on left
l HugeInt + long int
§ Can be member function
♦ When other way, need a global overloaded function
l long int + HugeInt
§ Cannot be a member function , must be global
16
Overloading Stream Insertion and Stream
Extraction Operators
l C++ can input and output fundamental types using the
<< and >> operators
l Example 1
n Class PhoneNumber
♦ Holds a telephone number
n Print out formatted number automatically
(123) 456-7890
17
1 // Fig. 11.3: PhoneNumber.h
2 // PhoneNumber class definition
3 #ifndef PHONENUMBER_H EXAMPLE 1
4 #define PHONENUMBER_H PhoneNumber.h
5
6 #include <iostream> (1 of 1)
7 using std::ostream;
8 using std::istream;
9
10 #include <string> Review Question:
11 using std::string; Why is the second argument
12 of the << function const?
13 class PhoneNumber
14 {
15 friend ostream &operator<<( ostream &, const PhoneNumber & );
16 friend istream &operator>>( istream &, PhoneNumber & );
17 private:
18 string areaCode; // 3-digit area code
19 string exchange; // 3-digit exchange
20 string line; // 4-digit line
21 }; // end class PhoneNumber
22 Notice function prototypes for overloaded operators
23 #endif >> and << (must be global, friend functions)
Why do the functions return Question: Why is this?
ostream& and istream& ?
18
1 // Fig. 11.4: PhoneNumber.cpp
2 // Overloaded stream insertion and stream extraction operators
3 // for class PhoneNumber.
4 #include <iomanip>
5 using std::setw;
6 Allows cout << phone; to be interpreted
7 #include "PhoneNumber.h" as: operator<<(cout, phone);
8
9 // overloaded stream insertion operator; cannot be
10 // a member function if we would like to invoke it with
11 // cout << somePhoneNumber; EXAMPLE 1
12 ostream &operator<<( ostream &output, const PhoneNumber &number )
PhoneNumber.cpp
13 {
14 output << "(" << number.areaCode << ") " (1 of 2)
15 << number.exchange << "-" << number.line;
16 return output; // enables cout << a << b << c; Display formatted phone number
17 } // end function operator<<
•The stream insertion operator function for operator << takes ostream reference
output and a const PhoneNumber reference number as arguments and
returns an ostream reference
19
18
20
19 // overloaded stream extraction operator; cannot be
20 // a member function if we would like to invoke it with
21 // cin >> somePhoneNumber;
22 istream &operator>>( istream &input, PhoneNumber &number ) ignore skips specified number of
23 { characters from input (1 by default)
24 input.ignore(); // skip (
25 input >> setw( 3 ) >> number.areaCode; // input area code
EXAMPLE 1
PhoneNumber.cpp
26 input.ignore( 2 ); // skip ) and space
27 input >> setw( 3 ) >> number.exchange; // input exchange (2 of 2)
28 input.ignore(); // skip dash (-)
29 input >> setw( 4 ) >> number.line; // input line Input each portion of
30 return input; // enables cin >> a >> b >> c; phone number separately
31 } // end function operator>>
•The parenthesis, dash and spaces are skipped by calling the istream member
function ignore, which discards the specified number of characters in the input
stream (one by default)
•When used with cin and strings, setw restricts the number of characters read in
in the next stream to the number specified by its argument
21
Software Engineering Observation
22
Overloading Unary Operators
l Overloading unary operators
n Can overload as non-static member
functions with no arguments
♦ So the unary just acts on an instance of the class
♦ Remember, static functions only access static
data
l Overloaded member functions need to access the
non-static members of the class
n OR Can overload as global function with one
argument
♦ Argument must be class object or reference to class
object
23
Overloading Unary Operators
l Upcoming example (we're going to overload the ! operator to test
whether or not an object of the string class we create is empty or
not)
n Overload ! to test for empty string
24
Overloading Binary Operators
l Overloading binary operators
n For a non-static member function, one
argument
n For a global function, two arguments
♦ One argument must be class object or reference
25
Overloading Binary Operators
l Upcoming example: Overloading += on the string class
26
Case Study: Array Class
l C++ built in arrays – all arrays before the new standard - (with a
C++ built in array, an array name is a pointer to the starting address
of the array) have a number of issues:
n No range checking
n Cannot be compared meaningfully with ==
n No array assignment (array names are const pointers)
n Arrays can't be output all at once but have to be stepped through element by element
n If array passed to a function, size must be passed as a separate argument
27
1 // Fig. 11.6: Array.h
2 // Array class for storing arrays of integers.
3 #ifndef ARRAY_H
4 #define ARRAY_H
5
6 #include <iostream>
Question: When is the
7 using std::ostream; copy constructor called?
8 using std::istream;
9
EXAMPLE #2
10 class Array
Array.h
11 {
12 friend ostream &operator<<( ostream &, const Array & ); (1 of 2)
13 friend istream &operator>>( istream &, Array & );
14 public:
15 Array( int = 10 ); // default constructor
Most operators overloaded as
16 Array( const Array & ); // copy constructor member functions (except << and
17 ~Array(); // destructor >>, which must be global functions)
18 int getSize() const; // return size
19
20 const Array &operator=( const Array & ); // assignment operator
21 bool operator==( const Array & ) const; // equality operator
22
Prototype for copy constructor
23 // inequality operator; returns opposite of == operator
24 bool operator!=( const Array &right ) const
25 {
26 return ! ( *this == right ); // invokes Array::operator==
27 } // end function operator!=
29
Array Example - comments
l Default Constructor
n Invoked whenever the compiler sees an instantiation of an
Array object, if no argument is input the default of 10 is
used
♦ The argument is for array size
n If an invalid argument is passed into the default
construction, the size is set to 10
n Uses new to obtain the memory needed for an array of the
size passed into the constructor or the default size of 10
♦ Must use new to dynamically allocate memory
n Uses the for loop to set all elements of the pointer based
array created with new to zero
♦ This is good programming practice to set a default value for
each array element
30
1 // Fig 11.7: Array.cpp
2 // Member-function definitions for class Array
3 #include <iostream>
4 using std::cerr;
5 using std::cout;
6 using std::cin; EXAMPLE #2
7 using std::endl; Array.cpp
8
9 #include <iomanip> (1 of 6)
10 using std::setw;
11
12 #include <cstdlib> // exit function prototype
13 using std::exit;
14
15 #include "Array.h" // Array class definition
16
17 // default constructor for class Array (default size 10)
18 Array::Array( int arraySize )
19 {
20 size = ( arraySize > 0 ? arraySize : 10 ); // validate arraySize
21 ptr = new int[ size ]; // create space for pointer-based array
22
23 for ( int i = 0; i < size; i++ )
24 ptr[ i ] = 0; // set pointer-based array element
25 } // end Array default constructor
31
Array Example - comments
l The Copy Constructor
n Remember this is a special constructor that intializes an object by making a copy of an existing object
♦ When dealing with pointers we don't want the memberwise assignment behavior the default copy
constructor performs so we must define our own copy constructor
l Question: Why is this?
§ To avoid leaving both Array objects pointing to the SAME dynamically allocated memory which is what would happen
with memberwise assignment
l Note that a copy constructor must receive its argument by reference, not
by value.
n Otherwise, the copy constructor call results in infinite recursion (a fatal logic error)
because receiving an object by value requires the copy constructor to make a copy
of the argument object.
n Recall that any time a copy of an object is required, the class’s copy constructor is
called.
n If the copy constructor received its argument by value, the copy constructor would
call itself recursively to make a copy of its argument!
32
Array Example - comments
l Copy constructor (summary)
n Used whenever copy of object is needed:
♦ Passing by/Returning by value (return value or parameter)
♦ Initializing an object with a copy of another of same type
l Array newArray( oldArray ); or
Array newArray = oldArray; (both are identical)
§ newArray is a copy of oldArray
n Prototype for copy constructor in class Array
♦ Array( const Array & );
♦ Must take reference
l Otherwise, the argument will be passed by value…
33
Array Example - comments
l The Destructor
n Invoked when an object of class Array goes out of
scope
n Uses delete to dynamically release memory
created by new in the constructor
34
26
27 // copy constructor for class Array;
28 // must receive a reference to prevent infinite recursion
29 Array::Array( const Array &arrayToCopy )
30 : size( arrayToCopy.size )
31 { EXAMPLE #2
32 ptr = new int[ size ]; // create space for pointer-based array Array.cpp
33
34 for ( int i = 0; i < size; i++ ) (2 of 6)
35 ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object
36 } // end Array copy constructor
37 We must declare a new integer array so the
38 // destructor for class Array objects do not point to the same memory
39 Array::~Array()
40 {
41 delete [] ptr; // release pointer-based array space
42 } // end destructor
43
44 // return number of elements of Array
45 int Array::getSize() const
46 {
47 return size; // number of elements in Array
48 } // end function getSize
35
Array Example - comments
l Overloaded Assignment Operator
n When the compiler sees an expression like this for two
Array objects integers1 and integers2:
♦ integers1 = integers2;
♦ It invokes the function operator = with the call:
integers1.operator=(integers2);
♦ This function is setting the argument on the left equal to the
argument on the right
36
49
50 // overloaded assignment operator;
51 // const return avoids: ( a1 = a2 ) = a3
52 const Array &Array::operator=( const Array &right )
53 {
54 if ( &right != this ) // avoid self-assignment
55 {
56 // for Arrays of different sizes, deallocate original
57 // left-side array, then allocate new left-side array Want to avoid self assignment
58 if ( size != right.size )
59 { This would be dangerous if this
60 delete [] ptr; // release space is the same Array as right
61 size = right.size; // resize this object
62 ptr = new int[ size ]; // create space for array copy because you could leave ptr
63 } // end inner if pointing to memory that has
64
been deallocated
65 for ( int i = 0; i < size; i++ )
66 ptr[ i ] = right.ptr[ i ]; // copy array into object
67 } // end outer if
68
69 return *this; // enables x = y = z, for example
70 } // end function operator=
EXAMPLE #2
♦This function is setting the argument on the left Array.cpp
equal to the argument on the right, copy the right
Array object into the left (3 of 6)
37
Array Example - comments
l Overloaded Equality (==) Operator
n When the compiler sees an expression like this for two
Array objects integers1 and integers2:
♦ integers1 == integers2;
♦ It invokes the function operator = with the call:
integers1.operator==(integers2);
♦ returns true if the two Array objects are equal (by equal we
mean the same size with the same element in each
corresponding array position)
38
71
72 // determine if two Arrays are equal and
73 // return true, otherwise return false
74 bool Array::operator==( const Array &right ) const
75 {
76 if ( size != right.size )
77 return false; // arrays of different number of elements
78
79 for ( int i = 0; i < size; i++ )
80 if ( ptr[ i ] != right.ptr[ i ] )
81 return false; // Array contents are not equal EXAMPLE #2
82
83 return true; // Arrays are equal Array.cpp
84 } // end function operator==
85 (4 of 6)
86 // overloaded subscript operator for non-const Arrays;
87 // reference return creates a modifiable lvalue
88 int &Array::operator[]( int subscript )
89 { integers1[ 5 ] calls
90 // check for subscript out-of-range error integers1.operator[]( 5 )
91 if ( subscript < 0 || subscript >= size )
92 {
93 cerr << "\nError: Subscript " << subscript
94 << " out of range" << endl;
95 exit( 1 ); // terminate program; subscript out of range
96 } // end if
97
98 return ptr[ subscript ]; // reference return
99 } // end function operator[]
39
100
101 // overloaded subscript operator for const Arrays
102 // const reference return creates an rvalue
103 int Array::operator[]( int subscript ) const
104 {
105 // check for subscript out-of-range error
106 if ( subscript < 0 || subscript >= size )
107 {
108 cerr << "\nError: Subscript " << subscript
109 << " out of range" << endl;
110 exit( 1 ); // terminate program; subscript out of range
EXAMPLE #2
111 } // end if Array.cpp
112
113 return ptr[ subscript ]; // returns copy of this element (5 of 6)
114 } // end function operator[]
115
116 // overloaded input operator for class Array;
117 // inputs values for entire Array
118 istream &operator>>( istream &input, Array &a )
119 {
120 for ( int i = 0; i < a.size; i++ )
121 input >> a.ptr[ i ];
122
123 return input; // enables cin >> x >> y;
124 } // end function
40
125
126 // overloaded output operator for class Array
127 ostream &operator<<( ostream &output, const Array &a )
128 {
129 int i;
130
131 // output private ptr-based array
132 for ( i = 0; i < a.size; i++ )
133 {
134 output << setw( 12 ) << a.ptr[ i ];
135
EXAMPLE #2
136 if ( ( i + 1 ) % 4 == 0 ) // 4 numbers per row of output Array.cpp
137 output << endl;
138 } // end for (6 of 6)
139
140 if ( i % 4 != 0 ) // end last line of output
141 output << endl;
142
143 return output; // enables cout << x << y;
144 } // end function operator<<
41
Array Class Client test Program
l Example 2:
n testArray.cpp (page 1 of 4)
n I changed this from the book slightly so this is my code with comments above explaining
#include <iostream>
#include <stdlib.h>
using namespace std;
#include "Array.h"
int main()
{
Array integers1(7); //instantiate a seven element array
Array integers2; //a ten element default array
//use overloaded inequality operator (remember this is an inline function, not implicit,
explicitly defined)
if(integers1 != integers2)
{
cout << "\nintegers1 and integers2 are NOT equal" << endl;
}
else
{
cout << "\nintegers1 and integers2 ARE equal" << endl;
}
else
{
cout << "\nintegers1 and integers2 are NOT equal" << endl;
}
//uncomment the part below to see what happens if invalid subscripts are
//attempted to be changed
//there should be a better design for this, I used the book design
system("PAUSE");
return 0;
};
Software Engineering Observation
A copy constructor, a destructor and an
overloaded assignment operator should
ALWAYS be provided as a group for any
class that uses dynamically allocated
memory.
47
Software Engineering Observation 11.6
It is possible to prevent one object of a class from
being assigned to another. This is done by
declaring the assignment operator as a private
member of the class.
48
Software Engineering Observation 11.7
It is possible to prevent class objects from being copied; to do
this, simply make both the overloaded assignment operator
and the copy constructor of that class private.
49