operator overloading version 1.0. objectives at the end of this lesson, students should be able to:...

53
Operator Overloading Version 1.0

Upload: merilyn-pitts

Post on 02-Jan-2016

220 views

Category:

Documents


0 download

TRANSCRIPT

Operator Overloading

Version 1.0

Objectives

At the end of this lesson, students should be able to: Write programs that correctly overload operators Describe how to overload a unary operator Tell how the compiler differentiates between pre- and post Describe how to overload a binary operator Explain when to use member vs non-member functions Explain when it is necessary to use friend functions Demonstrate how to return values when overloading operators Explain why operator overloading is useful Explain what the compiler generates when it sees an overloaded operator Demonstrate how constructors and overloaded cast operator are used to do conversions

In C++, operators are nothing more than functions,written with a different, but more familiar syntax.

Operator Overloading

We use operators to express fundamentaloperations in a concise and readable way.

For example,

y = x + y *z;

is much easier to read and write than

y.assign (x.add (y.multiply(z)));

The standard operators in C++ work onall of the basic data types. However, they do not work on user defined data types.

To add two objects of the Length class, wecould write a function called add, so we could write

lengthThree = lengthTwo.add (lengthOne);

But, this is awkward. Wouldn’t it be nicerto be able to write

lengthThree = lengthTwo + lengthOne;

We can do this if we overload the + operatorso that it works with objects of the Length class.

Example

Define the Length class as …

class Length{ public: Length( ); private: int feet; int inches;};

The Length class keeps track of a lengthmeasurement in feet and inches.

To overload the + operator so that we can addtwo Length objects together, we write a functionof the form:

Length operator+ (const Length&);

in this case the functionreturns a Length object.

the name of the functionis operator+

the function takesa const reference toa Length object asa parameter.

Now, if we write

lengthThree = LengthTwo + LengthOne;

The compiler looks in the Length class for afunction with the prototype

Length ::operator+ (const Length&);

and generates code just as if we had written

lengthThree = lengthTwo.operator+ (lengthOne);

the left hand operandbecomes the callingobject.

the right hand operandbecomes the parameterin the function call.

LengthThree = lengthTwo + lengthOne

All of the C++ operators can be overloadedexcept for the following:

. .* :: ?: sizeof

Because of the complexities involved, theoverloading of &&, ||, and the comma operatoris discouraged.

Key points to rememberYou cannot change the precedence ruleby overloading operators.

You cannot change the associativity of anoperator by overloading it.

It is not possible to create new operators.

You cannot change the way that the operatorswork on the basic data types.

You cannot change the “arity” of an operatorby overloading it.

Overloading Unary Functions

It certainly ought to be possible to overloadthe increment and decrement operators forthe Length class.

For example, we would probably like to be ableto do the following for a length object lengthOne:

lengthOne++;

The “makes sense” Rule

A programmer can clearly define what to doinside of the function that overloads anoperator. However, it is just good programmingpractice to be sure that the function makes goodsense.

So …

what does it mean to increment a Length?

Let’s choose the option of adding one inchto the current Length value.

This points out a complexity that we may not havethought of before. Suppose we have a Lengthobject whose value is 3 feet and 11 inches. Whathappens when we increment this object.

Obviously we want the result to be 4 feet. Thiscomplicates the code we would write, because wealways have to check to see if the addition of one inchresults in going to a new number of feet.

Let’s simplify the problem by re-defining the Lengthclass and take advantage of data hiding.

class Length{ public: Length( ); private: int inches;};

Because we only keep track of inches,we don’t have to worry here (or in any otherarithmetic operation) about handling theoverflow from inches to feet.

Now the code for the operator++ functionwould look something like:

const Length& Length::operator++( ){ ++inches; return *this;}

when written as a memberfunction, functions that overloadunary operators take no parameters.

the function works on the callingobject. In this case, the inchesdata member of the calling objectis incremented.

we return the callingobject (by reference).

Return TypesIn the increment example, why did we returna Length object, and why by reference?

In any expression, the result of some operation maybe used as an operand for another operator. Considerthe expression

Length someLength = ++lengthOne;

in this case, the result of incrementinglengthOne is used as the right handoperand for the assignment operator.So, the increment operator must returna Length object.

We pass by referencefor efficiency!

More Complications!

There are a couple of other issues to considerin this case.

1. How do we differentiate between a pre- and a post-increment?

2. Should we write the operator++ function as member function or as a non-member function?

pre- or post-increment

The code we just examined is for the pre-incrementoperator.

To tell the compiler that we want the function to befor the post-increment operator, we put a dummyparameter in the function prototype, as :

Length operator++ ( int );

this parameter is never actuallyused. It is simply a flag to thecompiler that this function is overloading the post-incrementoperator.

Length Length::operator++ (int){ Length tl = *this; ++inches; return tl;}

What’s going on here???

calling object,myLength

inches 5 myLength++;

Length Length::operator++ (int){ Length tl = *this; ++inches; return tl;}

the compiler invokesthe operator++(int)function

inches 5

Length object tlis local to thefunction

6

a copy of the object tl is puton the stack and returned tothe calling program. Why can’twe return a reference here?

cout << myLength++;

the result is that the copy of the originalobject (with the original value of inches)is sent to cout.

But, the inches value of myLengthis equal to 6. The effect is that myLengthgets incremented after (post-increment)the << operator is executed.

Member vs. Non-memberWhen overloading ( ), [ ], ->, or any of the assignmentoperators, the overloading operator function must bewritten as a member function.

For all other operators, we may write the function as amember or a non-member function.

If the lefthand operand is a basic data type, or an objectof a different class than the operator’s class, then theoperator overloading function must be a non-member function.

Non-member functions can be written as friendfunctions.

Friends

Friend functions are non-member functionsthat have all of the privileges of member functions. In particular, a friend function canaccess private data in the class that it is afriend of.

Friendship must be given!

class Length{ public: Length( ); friend Length& operator++ (Length& t);

private: int inches;};

the keyword friend tellsthe compiler that the functionoperator++ is a friend of theclass. It is a non-member function.

The function has access to theprivate data inches.

Length operator++ (Length& t){ t.inches++; return t;}

the function goes in a .cpp file.Note that there is no scope resolution operator nor a class name because the function doesnot belong to the class.

the code inside the functioncan directly access the inchesdata member, even though itis private.

don’t include the keyword friendhere. The function cannot claimit is a friend. This is done in the class definition.

because it is a non-memberfunction, we must pass the object to be worked on asa parameter.

Object Oriented purists don’t like friendfunctions because they break the rules ofencapsulation and data hiding.

If you want to be pure, provide accessorfunctions in the class and invoke themfrom the non-member function. However,this is less efficient than using a friendfunction.

Friend Classes

A class can be a friend of another class.When a class is declared as a friend, allof its member functions can access theprivate data of the class it is a friend of.

Overloading Binary Operators

A binary operator takes two operands.We will refer to these as the left-handoperand and the right-hand operand.

a = b + c;

operator

left-handoperand

right-handoperand

As a Member Function

Length Length::operator+ (const Length& rh){ Length tLen; tLen.inches = inches + rh.inches; return tLen;}

functions that overload binaryoperators, written as member functions, take one parameter,the right-hand operand.

this value comes from thecalling, or implicit object,the left-hand operand.

the result of addingtwo Length objectsought to be a Lengthobject.

a = b + c;

operator

left-handoperand

right-handoperand

for a, b, and c allLength objects …

b. operator+ (c);

The compiler generatesthe function invocation …

Nameless Temporary Objects

In the evaluation of the expression

a = b + c;

the operator+ function is calledto evaluate the right hand side of the equation.

a Length object is returnedon the stack. This object hasno name, and is oftentimescalled a nameless temporary object (nto).

finally the assignment isdone. By default, eachdata member of the ntois copied into the correspondingdata member of the object a.

after completing the assignment, the nto isremoved from the stackand no longer exists.

Commutative Operators

+ is a commutative operator. That is,

b + c;

is the same as

c + b;

But what if one operand is a basic datatype, for example, an integer. This probablymakes sense, since, for example, we can thinkof adding an integer, like 5, to a Length.

To make the operator commutative, we shouldbe able to write

Length c = myLength +5;

as well as

Length c = 5 + myLength;

but … how do we write the function in this case?

Rule of thumb:

If the piece of data that you would normallysend the message to (the left-hand operand)is a basic data type, then you have to overload the operator by writing a non-member function!

As a Non-member Function!

Length operator+ ( int lh, const Length& rh){ Length tLength; tLength.inches = lh + rh.inches; return tLength;}

functions that overload binary operatorstake two operands when written as a non-member function.

Overloading << and >>

<< and >> are binary operators where theleft-hand operator is always of a different classthan the one for which we are overloading theoperator. For example,

cout << myLength;

so .. we must write these as non-memberfunctions.

Because we want to represent a Length asfeet and inches externally, we must do someconversions. As a result, the function to overloadthe << operator might look like:

ostream &operator<< (ostream &out, const Length &rh){ int ft = rh.inches/12; int in = rh.inches %12; out << ft << “ft., “ << in << “in”; return out;}

out must be passedby reference because theostream class has nocopy constructor.

convert inches tofeet and inches.

the stream is returned so thatwe can cascade the << operator.

Cascading << out << ft << “ft., “ << in << “in”;

this expression is evaluated first, and returns the stream out.The entire expression may now be thought of as

out << “ft., “ << in << “in”;

where out already contains

out5

this expression is evaluated next, and returns the stream out.The entire expression may now be thought of as

out << “ft., “ << in << “in”;

where out already contains

out5 ft.

out << in << “in”;

this expression is evaluated next, and returns the stream out.The entire expression may now be thought of as

out << in << “in”;

where out already contains

out5 ft. 3

out << “in”;

finally this expression is evaluated and returns the stream out.The return value is not used in this last instance.

out << “in”;

out now contains

out5 ft. 3 in.

Stream Extraction

Overloading the Stream Extraction operator issimilar. Here, we have to decide how the userwill enter in the length.

In this example, I have assumed that the userwill enter the number of feet, followed by white space, and then the number of inches (I would probably prompt the user to enter the data this way).

istream &operator>> (istream &input, Length &rh){ int ft, in; input >> ft; input >> in;

rh.inches = in + 12 * ft; return input;}

Conversions and Overloading

When converting from a basic data type to auser defined class, the compiler looks fora constructor in the class that takes the base data type as a parameter.

Length::Length (const float r){ inches = r * 12;}

Now, if the programmer writes

myLength = 5.3;

the compiler invokes the constructor tocreate a nameless temporary object (nto). Theassignment from the nto to the object myLengthis then performed and the nto is thrown away.

Note: 5.3 is in feet!

When converting from a user defined class toa base data type, the compiler looks for afunction that overloads the cast operator.

Length::operator float( ){ int ft, in; ft = inches/12; in = inches %12; float r = (float)in/12.0; return ft + r;}

notice that no return typeis declared. The return typeis derived from the type ofcast being done.

Now if the programmer writes

float r = myLength;

The compiler invokes the function writtento overload the cast operator to do the conversion.

When converting from an object of one classto an object of another class, an explicit castis not always required. A statement like

orangeObject = appleObject;

will cause the compiler to look for a constructorin the Orange class that takes an Apple objectas a parameter, or a function in the Apple classthat overloads the cast operator to return anOrange object.

If both a constructor and a cast operatorare defined, the compiler will give anambiguity error. It does not know whichyou want it to use.