operator overloading and memory management. objectives at the conclusion of this lesson, students...

67
Operator Overloading and Memory Management

Post on 22-Dec-2015

218 views

Category:

Documents


1 download

TRANSCRIPT

Operator Overloading and

Memory Management

Objectives

At the conclusion of this lesson, students should be able to:

Overload C++ operators Explain why it is important to correctly manage dynamically allocated storage. Write programs that correctly use * Destructors to return dynamically allocated storage to the system. * Overloaded assignment operators to make a deep copy when necessary and return dynamically allocated storage to the system. * Copy constructors to make a deep copy when necessary.

Operator Overloading

In order to do write some of the code thatIs needed to manage memory correctly, wewill have to overload an operator in C++.

Suppose that we have a class called Region, That represents a rectangular region.

Region

- length: int- width: int

+ Region(int: int:)+ getWidth( ) :int+ getLength( ) :int

We know how to add two primitive datatypes together, but can we add two Regions?

Region r1(4,5);Region r2(6,3);Region r3 = r1 + r2:

We can, if we overload the + operator!

When we overload an operator, we tellthe compiler what to do when the operator

is used on objects of a given class.

The code to overload an operator can usuallybe written as a member function, or as a

non-member function of a class.

Overloading the + operator as a member Of the Region class.

The + operator is a binary operator – it has two operands.

Some Terminology

c = b + c;

the left-handoperand

theoperator

The right-handoperand

The + operator is a binary operator – it has two operands.

Some Terminology

c = b + c;

The message is sentto this object. It is

sometimes called theimplicit object.

theoperator

the right-hand operandis passed as a parameter

to the function.

The function looks like this:

Region Region::operator+(const Region& rho){ int newLength = length + rho.length; int newWidth = width + rho.width; Region rtnR(newWidth, newLength);}

Region Region::operator+(const Region& rho){ int newLength = length + rho.length; int newWidth = width + rho.width; Region rtnR(newWidth, newLength); return rtnR;}

The name of the function is the word“operator” followed by the symbol +

The function is a memberof the Region class

It returns a Region object.It is returned by value..

The function takes the right handoperand as its parameter. Remember topass objects by constant reference.

The compiler generates this function call

r3 = r1.operator+(r2);

length = 3width = 4

length = 5width = 2

r1 r2length = ?width = ?

r3

= + ;

The messageis sent to this object.

This object ispassed as theparameter

r3 = r1.operator+(r2);

length = 3width = 4

length = 5width = 2

r1 r2length = ?width = ?

r3

= + ;

The messageis sent to this object.

This object ispassed as theparameter

Region Region::operator+(const Region& rho){ int newLength = length + rho.length; int newWidth = width + rho.width; Region rtnR(newWidth, newLength);}

These belong to r1 (the implicit object )

You could write this function as anon-member function. Then it wouldlook like this:

r3 =operator+(r1, r2);

length = 3width = 4

length = 5width = 2

r1 r2length = ?width = ?

r3

= + ;

This object is passedas the 1st parameter

This object is passedas the 2nd parameter

Region operator+(const Region& lho, const Region& rho){ int newLength = lho.getLength( ) + rho.getLength( ); int newWidth = lho.getWidth( ) + rho.getWidth( ); Region rtnR(newWidth, newLength);} Must use a public getter

When overloading an operator there are timeswhen you cannot write the code as a member function.

A good example is the stream insertion operator.It must be written a non-member function.

If a member function, the compiler would generatethis function call

cout.operator<<(const Region& r1);

length = 3width = 4

r1cout

<< ;

but we can’t add code to the ostream classto overload the stream insertion operator.

Overloading the Stream Insertion Operator

Because we write this code as a non-memberfunction, it will take two parameters, like this:

ostream& operator<< (ostream& out, const Region& r1);

length = 3width = 4

r1cout

<< ;

The function mustreturn a stream object.

The stream parametercan’t be constant … we are changing it.

ostream& operator<< (ostream& out, const Region& r1){ out << “Width = “ << r1.getWidth( ); out += “Length = “ << r1.getLength( ); return out;}

length = 3width = 4

r1cout

<< ;

Put whatever data you wantinto the stream. You have to usepublic functions in the Region class.

Then just return the stream object.

Memory ManagementOne of the major program design issues in C++is memory management. The mishandling of dynamically allocated storage in C++ is amongthe most serious programming errors made whenusing the language.

Many of these issues can be addressed bydesigning classes as Concrete Data Types.

Concrete Data Types

One of the goals of good software development in C++ is to construct each class so that it appears, to the applications programmer, to be equivalent to a built-in type for the language.That is, it is well behaved in all of the ways that a standard built-in data type is well behaved.

A C++ class written in this way has been termed a “concrete data type''. Although in detail, the implementation of a class isspecific to the class, all concrete data types have a similar structure. Some author’s refer to this structure as the orthodox canonical class form.

C++ Programs can allocate objects in one of threememory areas.

Review

The run-time stackThe static data area or data segmentThe heap or free store

local variables

global and staticvariables

size and amountknown at compile

time

storage allocated at run-timebecause we don’t know how muchor what type of data will be stored

when the program is compiled.

An object is allocated on the heap using the new operator.

The allocated object has no name, but is referenced through a pointer returned by the new operator.

Storage allocated using new must be recycled back to the heap when the storage is no longer required. Storage that is no longer accessible, but has not been returned to the heap is called a memory leak.

Un-initialized pointers should be set to nullptr.

Pointers should also be set to nullptr after calling delete to return storage to the heap.

DestructorsAll Concrete Data Types must have a destructor if itmanages resources through a pointer.

When program execution reaches the end of a blockin which an object was declared, the storage allocatedfor that object on the stack is relinquished.

If a destructor is defined for the class to which the object belongs, the destructor is called first.

The purpose of the destructor is to clean up anyresources that the object may have acquired. Themost common resource that needs to be managed isstorage allocated from the heap.

Linked Lists

The concepts discussed in this slide set will be illustratedusing a linked list. Before going through the examples, it will be necessary that you understand what a linked list is and how they are used.

Memory Issues

list

3

node

12

node

7

node

9

This diagram illustrates an example ofa linked list. In this example, each node of thelist is dynamically allocated from the heap andcontains an integer value and a pointer to thenext node in the list.

nullptr

list

3

node

12

node

7

node

9

class List{ private: Node* head; int length; public: . . .};

the List class just containsa pointer to the first nodein the list, and an integer

containing the number ofelements in the list.

nullptr

list

3

node

12

node

7

node

9

class Node{ private: int data; Node* next; public: Node* getNext( ); …};

Each node object contains an integer data member and a pointer to the next node. The storage for each node is allocated from the heap as it is needed.

nullptr

list

3

node

12

node

7

node

9

So … what happens in this case when the listobject goes out of scope?

With no destructor, the pointer data memberin the list object is relinquished when the objectgoes out of scope. Without this pointer, the firstnode, and all subsequent nodes, become inaccessibleto the program. Since the storage for these nodes isstill owned by the program, we have a memory leak.

some block{ List myList; . . . blah … blah … blah . . .}

nullptr

list

3

node

12

node

7

node

9

Can you come up with a destructor that keepsThe memory leak from happening?

nullptr

list

3

node

12

node

7

node

9

head

length

data next

List::~List{

}

nullptr

list

3

node

12

node

7

node

9

The following destructor will solvethe problem.

List::~List( ){ Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; }}

head

length

data next

this function returns thevalue of next

nullptr

list

3

node

12

node

7

node

9

List::~List( ){ Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; }}

head

length

data next

this function returns thevalue of next

p pnext

nullptr

list

3

node

7

node

9

List::~List( ){ Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; }}

head

length

node

12

data next

this function returns thevalue of next

p pnext

nullptr

list

3

node

7

node

9

List::~List( ){ Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; }}

head

length

this function returns thevalue of next

p pnext

nullptr

Assignment Operator

We just illustrated how to manage the memoryallocated dynamically for Node objects when thelist goes out of scope. However, the automatic invocation of the destructor when the object goesout of scope introduces another serious problem.

Consider the following …

list

3

node

12

node

7

node

9

list_a

list

2

node

21

node

6

list_b

nullptr

nullptr

list

3

node

12

node

7

node

9

list_a

list

2

node

21

node

6

list_b

list_a = list_b;

the default assignment operator does a member-bymember copy of each data member in the list objects.

2

Problem: The pointer to thisData has been lost. Memory Leak!

nullptr

nullptr

list

2

node

12

node

7

node

9

list_a

list

2

node

21

node

6

list_b

Now … suppose list_b goes out of scope.

Our destructor, as specified, cleans upthe list, returning the storage for eachnode to the heap.

Problem: the pointerin list_a points to memoryno longer owned by the program.

nullptr

nullptr

list

2

node

12

node

7

node

9

list_a

Storage belonging to the heap.

Adding insult to injury, what happens when list_a goes out of scope?

this storage gets returned twice! Thiscould totally destroythe memory manager!

nullptr

We solve this problem by overloading theassignment operator in the List class.

The assignment operator must do two things:

list_a = list_b;

Free the storage used by the left operand (list_a) Make a copy the entire data structure of the right operand (list_b) and point to it in the left operand -– do a deep copy.

list

3

node

12

node

7

node

9

list_a

list

2

node

21

node

6

list_b

nullptr

nullptr

Free this storage

list

3list_a

list

2

node

21

node

6

list_b

nullptr

node

21

node

6nullptr

Make a copy the entire list …

2

const List& List::operator=(const List& b){ if (this ==&b) return *this;

it is customary to name the parameter ‘b’

always pass the operandas a constant reference

return a List referenceto allow multiple assignment

first, check to make surethat we are not doing theassignment a = a; We don’twant to clean up storage fora if this is the case.

const List& List::operator=(const List& b){ if (this ==&b) return *this;

Node* p = head; while (p != nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; }

this code cleans upthe storage allocatedto the left hand list. Note: It’s the same code we wrote for the destructor.

Next, we are going to do the copy. We aregoing to do what is called a deep copy. Thatis, we are going to create a complete copy of the data structure that is on the right hand side.

A shallow copy only copies pointers, not whatthey point to.

length = b.length;p = nullptr;Node* q = b.getHead( );while (q != nullptr){ Node* n = new Node; n->setNext (nullptr); n->setData (q->getData( )); if (p == nullptr) head = n; else p->setNext (n); p = n; q = q->getNext ( );}return *this;

start by copying thelength data. set the Node* p to nullptr

we will use this later.

then declare anotherNode* q, and set it to thehead data member in list b.

list

2

list_a

list

2

nodenode

6

list_b

q

n

pnullptr

21

length = b.length;P = nullptr;Node *q = b.getHead( );

length = b.length;p = nullptr;Node* q = b.getHead( );while (q != nullptr){ Node* n = new Node; n->setNext (nullptr); n->setData (q->getData( )); if (p == nullptr) head = n; else p->setNext (n); p = n; q = q->getNext ( );}return *this;

Now, allocate storage forthe first node in the newlist.

set its pointer to thenext node to nullptr.

get the data member of thecurrent node in the right-handlist and store its value in thisnew node.

list

2list_a

list

2

nodenode

6

list_b

q

n

pnullptr

nullptr

21

q points to the current nodein the right hand list

n points to the new node just created

n -> setNext (nullptr);n -> setData (q.getData( ));

Node *n = new Node;

21

length = b.length;p = nullptr;Node* q = b.getHead( );while (q != nullptr){ Node* n = new Node; n->setNext (nullptr); n->setData (q.getData( )); if (p == nullptr) head = n; else p->setNext (n); p = n; q = q->getNext ( );}return *this;

If this is the first nodestore its pointer in thelist object.

list

2list_a

list

2

nodenode

6

list_b

q

n

pnullptr

nullptr

21

head

if (p == NULL) head = n;

21

length = b.length;p = nullptr;Node* q = b.getHead( );while (q != nullptr){ Node* n = new Node; n->setNext (nullptr); n->setData (q.getData( )); if (p == nullptr) head = n; else p->setNext (n); p = n; q = q->getNext ( );}return *this;

set to point to the endnode in left hand list,the one we just created set to point to the next

node in the right hand list.

list

2list_a

list

2

nodenode

6

list_b

q

n

p

nullptr

head

p = n;

21

21

q = q->getNext( );

We have copied the List Object and thefirst Node. Since q is not null (it points toa node) go through the while block again.

list

2list_a

list

2

nodenode

list_b

list_a = list_b;

q

n

p

nullptr

head

21

21

Node* n = new Node;n->setNext (nullptr);n->setData (q.getData( ));

nullptr

6

list

2list_a

list

2

nodenode

list_b

list_a = list_b;

q

n

p

head

21

21

nullptr

6

6

else p->setNext (n);

p is not null, so …

nullptr

p = n;q = q->getNext ( );

We have successfully copied the secondnode from the right-hand list. q is now= nullptr, so we drop out of the loop.

Copy Constructor

We have fixed the assignment operator so that iscorrectly creates a copy of the object. However,objects also get copied when passed by value. When a function is called, all of the function arguments are copied into local variables associated with the function. When the functionexits, these variables go out of scope and are destroyed. This will cause similar problems to theones we just discussed with the default assignment operator.

list_a

3

node

12

node

7

node

9

Consider the list shown. What happens in thefunction invocation

double average (List a);

list_a

3

node

12

node

7

node

9

stack

when the function is called, a copyof the list object goes on the stack.The default is a shallow copy ….

3

list_a

3

node

12

node

7

node

9

stack

when the function exits, all of thevariables associated with the functiongo out of scope. This includes thecopy of the list object passed on thestack. When it goes out of scope, itsdestructor is called …

3

Oh-oh!

The Copy Constructor

List::List(const List& b){ length = b.length; Node* p =nullptr; Node* q = b.head; while (q != nullptr) { Node* n = new Node; n->getNext (nullptr); n->setData (q->getData( )); if (p == nullptr) head = n; else p->setNext (n); p = n; q = q.getNext( ); }}

If this looks familiar,it is because it is the samecode we used to copy theobject in the overloadedassignment operator.

this is a copy constructorbecause it takes an object of its own type as a parameter.

the compiler invokesthis code automaticallywhenever it needs to createa copy of the object.

Copy Constructor

It is important to note that like a normal constructor, the function of the copy constructor is to initialize the data members of the object that just got created.

The compiler generates the code to create the objectwhen you do a pass by value.

Factoring Common Code

There is a lot of common code between thedestructor, the assignment operator, and thecopy constructor. We can factor this commonfree and copy code out. Then the destructor,copy constructor, and assignment operatorlook as shown in the following slide.

List::List (const List& b){ copy(b);}

List::~List( ){ free( );}

const List& List::operator=(const List& b){ if (this != &b) { free( ); copy(b); } return *this;}