Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
34 views

Chapter 10 - Operator Overloading - Part 1

Operator overloading allows existing C++ operators like + and - to be redefined to work with user-defined classes. It makes programs more readable by allowing operations on objects to be written using familiar operator notation rather than function calls. Most operators can be overloaded as non-static member functions, with a few exceptions like . and ::. Overloading does not change an operator's precedence, associativity, arity, or behavior with built-in types. It only redefines the operator for user-defined class types.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views

Chapter 10 - Operator Overloading - Part 1

Operator overloading allows existing C++ operators like + and - to be redefined to work with user-defined classes. It makes programs more readable by allowing operations on objects to be written using familiar operator notation rather than function calls. Most operators can be overloaded as non-static member functions, with a few exceptions like . and ::. Overloading does not change an operator's precedence, associativity, arity, or behavior with built-in types. It only redefines the operator for user-defined class types.
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 49

Advanced C++

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.

l To redefine (overload) operators to work with objects of user-defined


classes.

l The differences between overloading unary and binary operators.

l To convert objects from one class to another class.

l When to, and when not to, overload operators.

l To see some examples of classes that demonstrate operator


overloading

l To use overloaded operators and other member functions of standard


library class string.

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

l Operator Overloading is a technique that allows us to use the operators in


C++ (+, -, *, ++ etc) to work with our classes (and instances of our classes)
n So instead of just overloading functions (mainly we've done this with constructors)
now we're going to overload C++ operators to perform operations on our objects
♦ We've already been doing this in our programs the difference is now we will learn to
program it in our own classes:
l So + will work one way with strings, one way with integers, etc….and another way
with our objects (Rectangle, Time, Date etc) based on how we define it

l The string class in C++ has a LOT of overloaded operators


n Think of how in C++ we can use the relational operators to compare strings and
we could use [] to "change" strings (strings in C++ are not immutable)
♦ This is because overloading is programmed into the class data type string!
l Remember we couldn't do that in Java

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 Anything we do using operator overloading can also be done with regular


member functions using explicit function calls
n However, the operator notation is often easier for programmers and remember the goal
of readability

l So we will learn to use operators with objects (operator overloading)


n Clearer than function calls for certain operations in classes
n Operator sensitive to context

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

l How to overload operators


n First write a NON-STATIC member function definition (overloaded operator
functions can not be static) or a global function definition (only use global if you
have to I'll show you when we have to) as you normally would EXCEPT:
♦ the function name will now start with the keyword operator, the word operator will be
followed by the symbol for the operator being overloaded
♦ Example
l operator+

n When operators are overloaded as member functions they must be non-static


♦ This is because they must be called on an object (instance) of the class and must operate
on that object
l Example: If we had our Rectangle class and had instances r1,r2 and r3
§ r3 = r1 + r2 would call the member function r1.operator+(r2) – this means it
MUST at on an instance using the dot operator – the left operand implicitly is used as the
instance that calls the function with the dot operator

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

l Overloading provides more concise notation than not overloading in many


cases
n object2 = object1.add( object2 );
vs.
object2 = object2 + object1;

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

Operators that can be overloaded

+ - * / % ^ & |
~ ! = < > += -= *=
/= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++
-- ->* , -> [] () new delete
new[] delete[]

Operators that cannot be overloaded

. .* :: ?:

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

l Operators must be overloaded explicitly


n Overloading + and = does not overload +=
n So overloading
♦ object1 + object2
♦ does NOT imply the following is overloaded
l object1 += object2 (would need EXPLICITY
overloaded)

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

l Certain operators MUST be overloaded as member functions


n Operators (), [], -> or any assignment operator (=) must be overloaded as a class
member function

l There are also certain operators that MUST be overloaded as global


members which we'll see in a minute

l The other overloadable operators may be overloaded as member


functions or global functions
n If you have a choice it's better to use OOP and overload as member functions
– this means for OUR class you should NEVER overload as a global if there’s a
choice, only use a global in a situation where there is no choice

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?

l When an operator function is overloaded as a member function:


1. the leftmost (or only in the case of unary) operand must be an object (or reference to an
object) of the operator's class
♦ Meaning the left operand of the + for example would have to be an object (or reference to) of the class
the operator is overloaded in
l So if you are overloading the + operator in a Rectangle class the operand on the left side of the + must be a
Rectangle instance in order to be overloaded as a member function (think of the r3 = r1 + r2 example we've been
talking about – the function is called using the r1 instance r1.operator+(r2)

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

l When an operator function MUST be overloaded as a global function


♦ If the leftmost (or only) operand needs to be a fundamental type or an object of a different class
this operator must be overloaded as a global function (so it can't be overloaded as a member
function in this case)
l We'll see that a global overloaded operator function can be made a friend if it needs to access private or
protected members of the class directly

l In general I would say only overload an operator as a global function if it can't be


overloaded as a member function for the reason explained above

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 is because the << operator is typically used in a way in which


n The left operand is an instance of type ostream &
♦ Such as cout object in cout << classObject

l Similarly the >> operator is typically used in a way in which


n The left operand is of type istream &
♦ Such as cin object in cin >> classObject

l Remember overloaded operators should mimic the functionality of their built-in


counterparts
n We don’t want to make them confusing for the programmer

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

l Performance Tip with operator functions


overloaded as global functions (like << and >>)
n It is possible to overload an operator as a global, non-
friend function but:
♦ such a function requiring access to a class’s private or
protected data would need to use set or get functions
provided in that class’s public interface
♦ The overhead of calling these functions could cause poor
performance, so these functions can be inlined to improve
performance (however that violates data hiding)
♦ Alternatively the function can be made a friend to avoid this
performance issue
l This is the best way to do it in my opinion – and in the
book's opinion

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

l So we have TWO reasons we may have to make our overloaded


functions global rather than member functions
1. The right side of the operator needs to be something other than an instance
of the class the operator is being overloaded in
2. commutative operators can cause #1 to happen as well

16
Overloading Stream Insertion and Stream
Extraction Operators
l C++ can input and output fundamental types using the
<< and >> operators

l The class libraries provided with C++ overload the <<


and >> operators for each fundamental type to process
each type correctly including pointers and C-Style strings

l You can overload these operators to perform input and


output for user-defined types

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

•Function operator << is used to display objects of type PhoneNumber

•When the compiler sees a call like this:


•cout << phone; (assuming phone is an object of type phone number
•it generates the global function call: operator<<(cout, phone);

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

•The function returns a reference variable input &input (ie cin).


•This enables input operations on phone number objects to be cascaded with
input operations on other Phone number objects or on objects of other data
types
•Like: cin >>phone1 >> phone2;
•or cin >> phone1 >> name; //name is a string
20
1 // Fig. 11.5: fig11_05.cpp
21
2 // Demonstrating class PhoneNumber's overloaded stream insertion
3 // and stream extraction operators.
4 #include <iostream>
5 using std::cout;
6 using std::cin;
7 using std::endl; EXAMPLE 1
testPhone.cpp
8
9 #include "PhoneNumber.h"
(1 of 1)
10
11 int main()
12 {
13 PhoneNumber phone; // create object phone
14
15 cout << "Enter phone number in the form (123) 456-7890:" << endl;
16
17 // cin >> phone invokes operator>> by implicitly issuing
18 // the global function call operator>>( cin, phone )
19 cin >> phone;
20
21 cout << "The phone number entered was: ";
Testing overloaded >> and <<
22 operators to input and output a
23 // cout << phone invokes operator<< by implicitly issuing PhoneNumber object
24 // the global function call operator<<( cout, phone )
25 cout << phone << endl;
26 return 0;
27 } // end main

21
Software Engineering Observation

l New input/output capabilities for user-


defined types are added to C++ without
modifying C++’s standard input/output
library classes
n This is another example of C++’s extensibility

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

n If implemented as a non-static member function, needs no arguments


♦ class String
{
public:
bool operator!() const;

};
♦ when the complier sees !s it generates the function call
s.operator!()

n If implemented as a global function , needs one argument


♦ bool operator!( const String & )
♦ when the complier sees !s it generates the function call operator!(s)

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

n If using a non-static member function, needs one argument


♦ class String
{
public:
const String & operator+=( const String & );

};
♦ when the complier sees y += z it generates the function call
y.operator+=( z )

n If using global function, needs two arguments


♦ const String &operator+=( String &, const String & );
♦ when the complier sees y += z it generates the function call
operator+=( y, z )

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

l We can use operator overloading and the other capabilities of


classes to create an Array class that uses overloading to
compensate for some of these issues
n We'll re-create the case study in the book

l In the Example: We Implement an Array class with


n Range checking
n Array assignment
n Arrays that know their own size
n Outputting/inputting entire arrays with << and >>
n Array comparisons with == and !=

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!=

!= operator simply returns opposite of ==


operator – only need to define the == operator 28
28
29 // subscript operator for non-const objects returns modifiable lvalue
30 int &operator[]( int );
31
32 // subscript operator for const objects returns rvalue
33 int operator[]( int ) const;
34 private:
35 int size; // pointer-based array size Operators for accessing specific
36 int *ptr; // pointer to first element of pointer-based array elements of Array object
37 }; // end class Array
38 EXAMPLE #2
39 #endif Array.h

Question: Why must << and >> be overloaded as global functions? (1 of 2)


Why are they overloaded as friend functions?

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 IMPORTANT: Copy constructors are invoked whenever a copy of an object is needed


including:
1. passing an object by value to a function
2. returning an object by value from a function
3. initializing an object with a copy of another object of the same class (the main one we've
discussed)

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!

l The argument to a copy constructor should be a const reference to allow a const


object to be copied.

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…

§ Which tries to make copy by calling copy constructor…Infinite


loop

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

l If we don't overload this we're stuck with


memberwise assignment with doesn't work
when we have pointers as member variables

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)

l Overloaded subscript operator


n We have two of these one for const arrays and one for
non const arrays
♦ Remember a program can only invoke const member
functions for a const object

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

cout << "ARRAY: integers1" << endl;


cout <<"--------------------" << endl;
cout << "Size of Array integers1 is: " << integers1.getSize() << endl;
//uses overloaded <<
cout << "Array integers1 after initialization: " << integers1 << endl;

cout << "\nARRAY: integers2" << endl;


cout <<"--------------------" << endl;
cout << "Size of Array integers2 is: " << integers2.getSize() << endl;
//uses overloaded <<
cout << "Array integers2 after initialization: " << integers2 << endl;

//get array input using overloaded >>


cout << "Enter " << integers1.getSize() << " integers with one space in between"
<< "\nto put in Array integers1: ";
cin >> integers1;

//get array input using overloaded >>


cout << "\nEnter " << integers2.getSize() << " integers with one space in between"
<< "\nto put in Array integers2: ";
cin >> integers2;
Array Class Client test Program
l Example 2:
n testArray.cpp (page 2 of 4)
n I changed this from the book slightly so this is my code with comments above explaining

cout << "\nARRAY: integers1" << endl;


cout <<"--------------------" << endl;
cout << "Array integers1 after input: " << integers1 << endl;

cout << "\nARRAY: integers2" << endl;


cout <<"--------------------" << endl;
cout << "Array integers2 after input: " << integers2 << endl;

//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;
}

//create Array integers3 using integers1 as an initializer, so we are using


//the copy constructor
Array integers3(integers1);//evokes the special constructor, the copy constructor

cout << "\nARRAY: integers3" << endl;


cout <<"--------------------" << endl;
cout << "Size of Array integers3 is: " << integers3.getSize() << endl;
//uses overloaded <<
cout << "Array integers3 after initialization with copy constructor: " << integers3 <<
endl;
Array Class Client test Program
l Example 2:
n testArray.cpp (page 3 of 4)
n I changed this from the book slightly so this is my code with comments above explaining

//use the overloaded = operator to assign integers2 to integers1


//afterwards they are the same
integers1 = integers2;

cout << "\nARRAY: integers1" << endl;


cout <<"--------------------" << endl;
cout << "Array integers1 after assignment operator executed: " << integers1 << endl;

cout << "\nARRAY: integers2" << endl;


cout <<"--------------------" << endl;
cout << "Array integers2 after assignment operator executed: " << integers2 << endl;

//use overloaded equality operator


if(integers1 == integers2)
{
cout << "\nintegers1 and integers2 ARE equal" << endl;
}

else
{
cout << "\nintegers1 and integers2 are NOT equal" << endl;
}

//using overloaded subscript operator


integers1[5] = 1000;
cout << "\nAfter the assignment, integers1[5] is: " << integers1[5] << endl;

cout << "\nARRAY: integers1" << endl;


cout <<"--------------------" << endl;
cout << "Array integers1 after changing integeres1[5]: " << integers1 << endl;
Array Class Client test Program
l Example 2:
n testArray.cpp (page 4 of 4)
n I changed this from the book slightly so this is my code with comments above explaining

//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

//attempt to change invalid subscript integers[15]


/*integers1[15] = 1000;
cout << "\nAfter the assignment, integers1[15] is: " << integers1[15] <<
endl;

cout << "\nARRAY: integers1" << endl;


cout <<"--------------------" << endl;
cout << "Array integers1 after attempting to change integeres1[15]: " <<
integers1 << endl;*/

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.

**NEW C++ STANDARD: There is a "move"


constructor and a "move" assignment operator
that may override the above rule but the book
doesn't discuss them until Chapter 24
n As far as tests go – the above rule is still THE RULE for
us
n The new standard is just something to keep in mind – it
will take a while for people to catch up
46
Common Programming Error 11.8

Not providing an overloaded assignment


operator and a copy constructor for a
class when objects of that class contain
pointers to dynamically allocated memory
is a logic error.

Not providing a destructor would also be a


logic error

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.

NEW C++11 STANDARD


lYou can now use delete to "delete" the assignment operator
function from your class to prevent one object of a class from being
assigned to another:
n You can do this for all the auto provided functionality such as copy
constructors and assignment operators
n For the assignment operator: Do this by putting code similar to the
following in the .h file where the prototypes are – example is for our Array
class example:
♦ const Array& operator=(const Array&) = delete;

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.

NEW C++11 STANDARD


lYou can now use delete to "delete" the assignment operator
function from your class to prevent one object of a class from being
assigned to another AND to prevent copy via the copy constructor:
n You can do this for all the auto provided functionality such as copy
constructors and assignment operators (you can delete any member
function but it makes most sense in these cases)
n For the assignment operator and copy constructor: Do this by putting
code similar to the following in the .h file where the prototypes are –
example is for our Array class example:
♦ const Array& operator=(const Array&) = delete;
♦ Array(const Array&) = delete;

49

You might also like