1 / 67

Operator Overloading and Memory Management

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

Download Presentation

Operator Overloading and Memory Management

An Image/Link below is provided (as is) to download presentation 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. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Operator Overloading andMemory Management

  2. 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.

  3. Operator Overloading In order to do write some of the code that Is needed to manage memory correctly, we will have to overload an operator in C++.

  4. 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

  5. We know how to add two primitive data types 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 tell the compiler what to do when the operator is used on objects of a given class.

  6. The code to overload an operator can usually be written as a member function, or as a non-member function of a class.

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

  8. Some Terminology The + operator is a binary operator – it has two operands. c = b + c; the left-hand operand The right-hand operand the operator

  9. Some Terminology The + operator is a binary operator – it has two operands. c = b + c; The message is sent to this object. It is sometimes called the implicit object. the right-hand operand is passed as a parameter to the function. the operator

  10. The function looks like this: Region Region::operator+(const Region& rho) { intnewLength = length + rho.length; intnewWidth = width + rho.width; Region rtnR(newWidth, newLength); }

  11. The name of the function is the word “operator” followed by the symbol + The function is a member of the Region class The function takes the right hand operand as its parameter. Remember to pass objects by constant reference. It returns a Region object. It is returned by value.. Region Region::operator+(const Region& rho) { intnewLength = length + rho.length; intnewWidth = width + rho.width; Region rtnR(newWidth, newLength); return rtnR; }

  12. r1 r2 r3 length = 5 width = 2 length = 3 width = 4 length = ? width = ? = + ; The message is sent to this object. This object is passed as the parameter The compiler generates this function call r3 = r1.operator+(r2);

  13. r1 r2 r3 length = 5 width = 2 length = 3 width = 4 length = ? width = ? = + ; The message is sent to this object. This object is passed as the parameter r3 = r1.operator+(r2); These belong to r1 (the implicit object ) Region Region::operator+(const Region& rho) { intnewLength = length + rho.length; intnewWidth = width + rho.width; Region rtnR(newWidth, newLength); }

  14. r1 r2 r3 length = 5 width = 2 length = 3 width = 4 length = ? width = ? = + ; This object is passed as the 1st parameter This object is passed as the 2nd parameter You could write this function as a non-member function. Then it would look like this: r3 =operator+(r1, r2); Region operator+(const Region& lho, const Region& rho) { intnewLength = lho.getLength( ) + rho.getLength( ); intnewWidth = lho.getWidth( ) + rho.getWidth( ); Region rtnR(newWidth, newLength); } Must use a public getter

  15. When overloading an operator there are times when 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.

  16. r1 cout length = 3 width = 4 << ; If a member function, the compiler would generate this function call cout.operator<<(const Region& r1); but we can’t add code to the ostream class to overload the stream insertion operator.

  17. Overloading the Stream Insertion Operator

  18. r1 cout length = 3 width = 4 << ; Because we write this code as a non-member function, it will take two parameters, like this: ostream& operator<< (ostream& out, const Region& r1); The function must return a stream object. The stream parameter can’t be constant … we are changing it.

  19. r1 cout length = 3 width = 4 << ; ostream& operator<< (ostream& out, const Region& r1) { out << “Width = “ << r1.getWidth( ); out += “Length = “ << r1.getLength( ); return out; } Put whatever data you want into the stream. You have to use public functions in the Region class. Then just return the stream object.

  20. Memory Management One of the major program design issues in C++ is memory management. The mishandling of dynamically allocated storage in C++ is among the most serious programming errors made when using the language. Many of these issues can be addressed by designing classes as Concrete Data Types.

  21. 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 is specific to the class, all concrete data types have a similar structure. Some author’s refer to this structure as the orthodox canonical class form.

  22. local variables size and amount known at compile time global and static variables Review C++ Programs can allocate objects in one of three memory areas. The run-time stack The static data area or data segment The heap or free store storage allocated at run-time because we don’t know how much or what type of data will be stored when the program is compiled.

  23. 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.

  24. Un-initialized pointers should be set to nullptr. Pointers should also be set to nullptr after calling delete to return storage to the heap.

  25. Destructors All Concrete Data Types must have a destructor if it manages resources through a pointer. When program execution reaches the end of a block in which an object was declared, the storage allocated for 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 any resources that the object may have acquired. The most common resource that needs to be managed is storage allocated from the heap.

  26. Linked Lists The concepts discussed in this slide set will be illustrated using 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.

  27. Memory Issues node node node 3 12 7 list 9 nullptr This diagram illustrates an example of a linked list. In this example, each node of the list is dynamically allocated from the heap and contains an integer value and a pointer to the next node in the list.

  28. node node node 3 12 7 list 9 nullptr class List { private: Node* head; int length; public: . . . }; the List class just contains a pointer to the first node in the list, and an integer containing the number of elements in the list.

  29. node node node 3 12 7 list 9 nullptr 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.

  30. 3 list node node node 12 7 9 nullptr So … what happens in this case when the list object goes out of scope? With no destructor, the pointer data member in the list object is relinquished when the object goes out of scope. Without this pointer, the first node, and all subsequent nodes, become inaccessible to the program. Since the storage for these nodes is still owned by the program, we have a memory leak. some block { List myList; . . . blah … blah … blah . . . }

  31. 3 list node node node 12 7 9 nullptr Can you come up with a destructor that keeps The memory leak from happening?

  32. 3 list node node node head length 12 7 9 nullptr data next List::~List { }

  33. 3 list The following destructor will solve the problem. node node node head length 12 7 9 nullptr data next List::~List( ) { Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; } } this function returns the value of next

  34. p pnext 3 list node node node head length 12 7 9 nullptr data next List::~List( ) { Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; } } this function returns the value of next

  35. p pnext node 3 12 list data next node node head length 7 9 nullptr List::~List( ) { Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; } } this function returns the value of next

  36. pnext 3 list p node node head length 7 9 nullptr List::~List( ) { Node* p = head; while ( p!=nullptr) { Node* pnext = p->getNext( ); delete p; p = pnext; } } this function returns the value of next

  37. Assignment Operator We just illustrated how to manage the memory allocated dynamically for Node objects when the list goes out of scope. However, the automatic invocation of the destructor when the object goes out of scope introduces another serious problem. Consider the following …

  38. node node node list_a 3 12 7 list 9 nullptr node node list_b 2 21 6 list nullptr

  39. list_a = list_b; Problem: The pointer to this Data has been lost. Memory Leak! node node node list_a 3 12 7 list 9 nullptr node node list_b 2 2 21 6 list nullptr the default assignment operator does a member-by member copy of each data member in the list objects.

  40. Now … suppose list_b goes out of scope. node node node list_a 2 12 7 list 9 nullptr node node Problem: the pointer in list_a points to memory no longer owned by the program. list_b 2 21 6 list nullptr Our destructor, as specified, cleans up the list, returning the storage for each node to the heap.

  41. Adding insult to injury, what happens when list_a goes out of scope? node node node list_a 2 12 7 list 9 nullptr this storage gets returned twice! This could totally destroy the memory manager! Storage belonging to the heap.

  42. We solve this problem by overloading the assignment 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.

  43. Free this storage node node node list_a 3 12 7 list 9 nullptr node node list_b 2 21 6 list nullptr

  44. node node 21 6 nullptr list_a 2 3 list node node list_b 2 21 6 list nullptr Make a copy the entire list …

  45. it is customary to name the parameter ‘b’ return a List reference to allow multiple assignment always pass the operand as a constant reference const List& List::operator=(const List& b) { if (this ==&b) return *this; first, check to make sure that we are not doing the assignment a = a; We don’t want to clean up storage for a if this is the case.

  46. 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 up the storage allocated to the left hand list. Note: It’s the same code we wrote for the destructor.

  47. Next, we are going to do the copy. We are going to do what is called a deep copy. That is, 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 what they point to.

  48. start by copying the length data. set the Node* p to nullptr we will use this later. 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; then declare another Node* q, and set it to the head data member in list b.

  49. length = b.length; P = nullptr; Node *q = b.getHead( ); q list_a n p nullptr list node node list_b 2 2 21 6 list

  50. 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 for the first node in the new list. set its pointer to the next node to nullptr. get the data member of the current node in the right-hand list and store its value in this new node.

More Related