1 / 53

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

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript

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

Explain what the compiler generates when it sees an

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.

We use operators to express fundamental

operations in a concise and readable way.

For example,

y = x + y *z;

is much easier to read and write than

The standard operators in C++ work on

all of the basic data types. However, they

do not work on user defined data types.

To add two objects of the Length class, we

could write a function called add, so we

could write

But, this is awkward. Wouldn’t it be nicer

to be able to write

lengthThree = lengthTwo + lengthOne;

We can do this if we overload the + operator

so that it works with objects of the Length class.

Define the Length class as …

class Length

{

public:

Length( );

private:

int feet;

int inches;

};

The Length class keeps track of a length

measurement in feet and inches.

two Length objects together, we write a function

of the form:

Length operator+ (const Length&);

the name of the function

is operator+

in this case the function

returns a Length object.

the function takes

a const reference to

a Length object as

a parameter.

Now, if we write

lengthThree = LengthTwo + LengthOne;

The compiler looks in the Length class for a

function with the prototype

Length ::operator+ (const Length&);

LengthThree = lengthTwo + lengthOne

and generates code just as if we had written

lengthThree = lengthTwo.operator+ (lengthOne);

the left hand operand

becomes the calling

object.

the right hand operand

becomes the parameter

in the function call.

All of the C++ operators can be overloaded

except for the following:

. .* :: ?: sizeof

Because of the complexities involved, the

is discouraged.

Key points to remember

You cannot change the precedence rule

You cannot change the associativity of an

It is not possible to create new operators.

You cannot change the way that the operators

work on the basic data types.

You cannot change the “arity” of an operator

It certainly ought to be possible to overload

the increment and decrement operators for

the Length class.

For example, we would probably like to be able

to do the following for a length object lengthOne:

lengthOne++;

The “makes sense” Rule

A programmer can clearly define what to do

inside of the function that overloads an

operator. However, it is just good programming

practice to be sure that the function makes good

sense.

So …

what does it mean to increment a Length?

Let’s choose the option of adding one inch

to the current Length value.

This points out a complexity that we may not have

thought of before. Suppose we have a Length

object whose value is 3 feet and 11 inches. What

happens when we increment this object.

Obviously we want the result to be 4 feet. This

complicates the code we would write, because we

always have to check to see if the addition of one inch

results in going to a new number of feet.

Let’s simplify the problem by re-defining the Length

class 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 other

overflow from inches to feet.

Now the code for the operator++ function

would look something like:

const Length& Length::operator++( )

{

++inches;

return *this;

}

when written as a member

unary operators take no parameters.

the function works on the calling

object. In this case, the inches

data member of the calling object

is incremented.

we return the calling

object (by reference).

in this case, the result of incrementing

lengthOne is used as the right hand

operand for the assignment operator.

So, the increment operator must return

a Length object.

Return Types

In the increment example, why did we return

a Length object, and why by reference?

We pass by reference

for efficiency!

In any expression, the result of some operation may

be used as an operand for another operator. Consider

the expression

cout << ++lengthOne;

More Complications!

There are a couple of other issues to consider

in 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-increment

operator.

To tell the compiler that we want the function to be

for the post-increment operator, we put a dummy

parameter in the function prototype, as :

Length operator++ ( int );

this parameter is never actually

used. It is simply a flag to the

compiler that this function is

operator.

Length Length::operator++ (int)

{

Length tl = *this;

++inches;

return tl;

}

What’s going on here???

Why is the return different

the compiler invokes

the operator++(int)

function

Length Length::operator++ (int)

{

Length tl = *this;

++inches;

return tl;

}

inches

5

Length object tl

is local to the

function

myLength++;

inches

5

6

calling object,

myLength

a copy of the object tl is put

on the stack and returned to

the calling program. Why can’t

we return a reference here?

cout << myLength++;

the result is that the copy of the original

object (with the original value of inches)

is sent to cout.

But, the inches value of myLength

is equal to 6. The effect is that myLength

gets incremented after (post-increment)

the << operator is executed.

Member vs. Non-member

When overloading ( ), [ ], ->, or any of the assignment

written as a member function.

For all other operators, we may write the function as a

member or a non-member function – your choice.

If the lefthand operand is a basic data type, or an object

of a different class than the operator’s class, then the

function.

Non-member functions can be written as friend

functions.

Friends

Friend functions are non-member functions

that have all of the privileges of member

functions. In particular, a friend function can

access private data in the class that it is a

friend of.

Friendship must be given!

class Length

{

public:

Length( );

friend Length& operator++ (Length& t);

private:

int inches;

};

the keyword friend tells

the compiler that the function

operator++ is a friend of the

class. It is a non-member function.

private data inches.

because it is a non-member

function, we must pass the

object to be worked on as

a parameter.

the function goes in a .cpp file.

Note that there is no scope

resolution operator nor a class

name because the function does

not belong to the class.

Length operator++ (Length& t)

{

t.inches++;

return t;

}

the code inside the function

can directly access the inches

data member, even though it

is private.

don’t include the keyword friend

here. The function cannot claim

it is a friend. This is done in the

class definition.

Object Oriented purists don’t like friend

functions because they break the rules of

encapsulation and data hiding.

If you want to be pure, provide accessor

functions in the class and invoke them

from the non-member function. However,

this is less efficient than using a friend

function.

Friend Classes

A class can be a friend of another class.

When a class is declared as a friend, all

of its member functions can access the

private data of the class it is a friend of.

A binary operator takes two operands.

We will refer to these as the left-hand

operand and the right-hand operand.

operator

a = b + c;

right-hand

operand

left-hand

operand

As a Member Function

operators, written as member

functions, take one parameter,

the right-hand operand.

two Length objects

ought to be a Length

object.

Length Length::operator+ (const Length& rh)

{

Length tLen;

tLen.inches = inches + rh.inches;

return tLen;

}

this value comes from the

calling, or implicit object,

the left-hand operand.

for a, b, and c all

Length objects …

operator

a = b + c;

b. operator+ (c);

The compiler generates

the function invocation …

right-hand

operand

left-hand

operand

the operator+ function is called

to evaluate the right hand side

of the equation.

Nameless Temporary Objects

In the evaluation of the expression

a = b + c;

finally the assignment is

done. By default, each

data member of the nto

is copied into the corresponding

data member of the object a.

a Length object is returned

on the stack. This object has

no name, and is oftentimes

called a nameless temporary

object (nto).

after completing the

assignment, the nto is

removed from the stack

and 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 data

type, for example, an integer. This probably

makes sense, since, for example, we can think

of adding an integer, like 5, to a Length.

To make the operator commutative, we should

be 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 normally

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

take two operands when written as a non-

member function.

Length operator+ ( int lh, const Length& rh)

{

Length tLength;

tLength.inches = lh + rh.inches;

return tLength;

}

<< and >> are binary operators where the

left-hand operator is always of a different class

operator. For example,

cout << myLength;

so .. we must write these as non-member

functions.

Because we want to represent a Length as

feet and inches externally, we must do some

conversions. As a result, the function to overload

the << operator might look like:

out must be passed

by reference because the

ostream class has no

copy constructor.

ostream &operator<< (ostream &out, const Length &rh)

{

int ft = rh.inches/12;

int in = rh.inches %12;

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

return out;

}

convert inches to

feet and inches.

the stream is returned so that

we can cascade the << operator.

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”;

5

out

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

this expression is evaluated next, and returns the stream out.

The entire expression may now be thought of as

out << in << “in”;

5 ft.

out

out << in << “in”;

this expression is evaluated next, and returns the stream out.

The entire expression may now be thought of as

out << “in”;

5 ft. 3

out

out << “in”;

finally this expression is evaluated and returns the stream out.

The return value is not used in this last instance.

out now contains

5 ft. 3 in.

out

Stream Extraction

similar. Here, we have to decide how the user

will enter in the length.

In this example, I have assumed that the user

will 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;

}

When converting from a basic data type to a

user defined class, the compiler looks for

a 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 to

create a nameless temporary object (nto). The

assignment from the nto to the object myLength

is then performed and the nto is thrown away.

Note: 5.3 is in feet!

When converting from a user defined class to

a base data type, the compiler looks for a

function 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 type

is declared. The return type

is derived from the type of

cast being done.

Now if the programmer writes

float r = myLength;

The compiler invokes the function written

to overload the cast operator to do the conversion.

When converting from an object of one class

to an object of another class, an explicit cast

is not always required. A statement like

orangeObject = appleObject;

will cause the compiler to look for a constructor

in the Orange class that takes an Apple object

as a parameter, or a function in the Apple class

that overloads the cast operator to return an

Orange object.

If both a constructor and a cast operator

are defined, the compiler will give an

ambiguity error. It does not know which

you want it to use.