operator overloading and memory management. objectives at the conclusion of this lesson, students...
Post on 22-Dec-2015
218 views
TRANSCRIPT
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.
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.
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
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
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
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.
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.
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.