600 likes | 619 Views
Advanced Issues on Classes and OOP. Part 1 - constructor, destructor, copy constructor, deep copy, assignment operator - Tapestry Section 12.3.5 and partially 12.3.3 (a bit limited) - Horton parts of Chapter 7 and 8 (detailed discussion and
E N D
Advanced Issues on Classesand OOP Part 1 - constructor, destructor, copy constructor, deep copy, assignment operator -Tapestry Section 12.3.5 and partially 12.3.3 (a bit limited) - Horton parts of Chapter 7 and 8 (detailed discussion and some parts are not needed) operator overloading Horton Chapter 8, starting page 345 (a bit better) Appendix E and 9.4 in Tapestry Part 2 - iterators and friend classes, using static variables in classes Part 3 sharing a variable among several objects of the same class (reference variables, pointers)
Constructor Constructors are special member fuctions of a class. When an object of a class is created, C++ calls the constructorfor that class. Properties: • Constructors have the same name as the class. • Constructors do not return any values • Constructors are invoked first when an object is initialized. Any initializations for the class members, memory allocations are done in the constructor. Constructors may or may not take parameters • A constructor with no parameter is called default constructor
DefaultConstructor Constructorwith no parameter. Node::Node() { . . . //initializationcode } A constructorwithparameters can also be defaultifthedefaultvalues of theseparametersareprovided at declaration. Inthiscase, if no argument is providedwhentheconstructor is called, defaultvaluesareused. Ifargument(s) is/areprovided, givenargumentsareusedduringconstruction. Node::Node(intnum = 0, Node * ptr = NULL) { . . . //initializationcode } NodemyNode(); NodemyOtherNode (10); Node * ptr = newNode(); Node * ptr2 = newNode(10, ptr); Node * ptr3 = newNode (100);
DefaultConstructor What happens if the programmer does not define any constructor (with and without parameters)? • The compiler attempts to generate a default constructor • Not visible to the programmer • When an object is created, this default constructor is invoked automatically • This compiler-generated constructor allocates memory for the class private members and calls default constructors of them (if applicable) • Dynamic memory allocations are not performed automatically.
//point.h #ifndef POINT_H #define POINT_H class Point { public: Point();//default constructor Point(int xx, int yy); // constructor with parameters . . . private: int x; int y; }; #endif //default constructor Point::Point() { x = 0; y = 0; } // constructor with parameters Point::Point(int xx, int yy) { x = xx; y = yy; } ConstructorExample Instead of thesetwoconstructorsyou can have Point::Point(intxx=0, intyy=0) { x = xx; y = yy; } If no constructorwasdefined, then a createdobjectwouldhavetwointegers x andywith no initialvalues
UsingInitializerList in Constructor //default constructor Point::Point() : x(0), y(0) { } //Alternativedefaultconstructorimplementation Point::Point() : x(0) { y = 0; } // constructorwithparameters Point::Point(intxx, intyy) :x(xx), y(yy) { } Syntax ConstructorHeader: privatedata (value), privatedata(value), . . . { // other constructor code comes here, executed after assigning values in the initializer list } Examples
DestructorTapestrypp. 624-625 Each class should also have a destructor, which should take care of returning any remaining dynamically allocated memory back to heap. The destructor has name ~classname()(put ~ character before the class name) • No parameters, no return value • This is in the class definition under public class LinkedList { private: node * head; int size; public: LinkedList (); ~LinkedList (); //destructor void printList(); void addToBeginning(int n); //more functions here };
Destructor Destructor function is conceptually the inverse of constructor They are called when an object is destroyed. Typical job is to return dynamic memory allocated (using new, malloc, calloc) to the heap. You do not need to take an action for automatic (regular) data members (i.e. the ones allocated from the runtime stack). They are automatically deallocated. LinkedList::~LinkedList () { node * ptr = head; while (ptr != NULL) { node * temp = ptr->next; delete ptr; ptr = temp; } }
Destructor The destructor is called automatically when the object goes out of scope. • For local (automatic) objects, when the block in which the object is created finishes • For static or global objects, when the program finishes • For dynamically allocated objects, when they are deleted using delete or free If you do not provide a destructor for your class, the compiler provides one, but that’s just an empty function, so it does not serve any purpose beside providing a required function (but dummy). This may be enough when your class object does not use any heap memory. For instance a Date class that has only int, day, month, year member variables does not need anything specific in its destructor. You can also explicitly call a destructor, but this is not so needed in practice. LinkedList mylist; . . . mylist.~LinkedList(); //mylist destroyed
Destructor int CountUnique (ifstream & input) { string word; LinkStringSet set; //similar to a linkedlist of strings; also has a size field // LinkStringSet is a Tapestry class while (input >> word) //by inserting words into a “set” which set.insert(word); //skips the insertion if the element is already in the set //we can count how many elements (words) are unique return set.size(); } What happens to setwhen this function returns? At the end of the function, destructor of LinkStringSet class is called on set since setgoes out of scope.
Destructor Since the compiler makes this call automatically, it creates a dummy destructor for each class that does not contain a destructor. So, • if the programmer has not supplied a destructor, the one which is created by the compiler is called • This basically prevents the program from not compiling, but as a dummy function, it is not guaranteed to do the right thing (e.g. doesn’t free dynamically allocated memory) • if the programmer has supplied one, compiler won’tgenerate a dummy destructor • Remember that the (real) destructor should free all memory taken with “new” or other dynamic memory allocation functions. Let'sseelinkedlistextraclassanddemo (linkedlistextra.cpp and linkedlistextrademo.cpp) forliveexamples
CopyConstructor, OperatorOverloading, AssignmentOverviewandPurpose Now that you know how to write a class, with constructor, destructor and necessary member functions, we will look at more fancy material. We are going to see how the functionality shown in red, can be achieved: linkedlist list1, list2, list3; list1.InsertOrdered(5); list2.PrintList(); //you know how to do these … list2 = list1; //assign list1 to list2 list3 = list1 + list2; //addition of lists, with suitable meaning linkedlistlist4(list1); //construct list4 from list1 First two are typically operators that you take for granted for built-in types. Now you will see how to give the same functionality for any class (as long as meaningful for your class).
Copy Constructor Special constructor called when an object is first declared and initialized using another object of the same type Example: LinkedList list3(list1);//list3 is created as a copy of list1 //list1 was previously defined Date today; Date tomorrow (today+1); Date yesterday (today-1); yesterday = tomorrow; //this is *not* a copy constructor Syntax Examples for the Copy constructor definition (also add a prototype to class definiton): LinkedList::LinkedList (const LinkedList & copy) { //copy constructor code comes here } Date::Date (const Date & d) { //copy constructor code comes here }
Copy ConstructorforLinkedListClass Youhavetoadd a prototypetotheclassdefinition as well. Caution: Alwaysuseconstreferenceparameterhere. classLinkedList { private: node * head; int size; public: LinkedList (); LinkedList (const LinkedList &); //copy constructor ~LinkedList (); //destructor voidprintList() const; voidaddToBeginning(int n); // morefunctionshere }; LinkedList::LinkedList (constLinkedList & copy) { head = copy.head; size = copy.size; }
node * head int size = 3 node * head int size = 3 node * head int size = 3 CopyConstructor list1 3 4 5 list1 3 4 5 list2 Thecopyconstructor in thepreviousslidemakes"shallowcopy" ; onlytheprivate data of a classinstance is copied. • Inthecase of a linkedlist, thiswould be theheadpointer, NOT thewholelist • Thenewlistsharesthesamememory as theoldone! Let'sgive an example Suppose list1 is likethe one on theright. AfterLinkedList list2(list1);thelistsbecome as follows
Copy Constructor For every class, there is adefault copy constructor(compiler provided). • Compiler provides this if you do not declare a copy constructor. • Work exactly as shallow copy mentioned before • Simply copies the value of each instance variable(e.g. head pointer, size for linked lists) from one object to the other. • The entire list is not copied. Shallow copy may not be what we want. It may cause several problems. • See next slide and let's see linkedlistextra class and demo (linkedlistextra.cpp and linkedlistextrademo.cpp) for example cases. • Also try that we do not actually need to define a copy constructor for shallow copy; default copy constructor would do the same job. It is alwaysbettertouseconstreferenceparametersinstead of valueparametersforclasstypes. Invalueparameterpassing, implicitlycopyconstructor is invoked. Usingconstreferenceparameteravoidsthisandunexpectedeffects of default/shallowcopyconstructorsanddestructors.
WhyShallowCopy is Bad? LinkedList list1; for (int k=0; k < 4; k++) { list1.addToBeginning(k+1); } list1.printList(); LinkedList list2(list1); list1.deleteList(); list2.printList(); We want to be able to do this! But the program crashes since the list is deleted from the memory after list1.deleteList(); and list2.printList(); tries to access the deleted linked list.
node * head int size = 3 node * head int size = 3 GoodCopy Constructor – DeepCopy If the object has dynamically allocated memory, such as linked lists, you may want to copy the entire memory location (the linked list) to another object • You create a clone • called "deep copy" since you trace all memory locations that the object is using. • You have to write the necessary function as a deep copy constructor (not compiler provided) LinkedList list2(list1); list1 3 4 5 list2 3 4 5
Deep Copy – How? No change in theclassdefinition. Onlyimplementation is different • Seethisandnextslide • Also see linkedlistextra class and demo (linkedlistextra.cpp) classLinkedList { private: node * head; int size; public: LinkedList (); LinkedList (const LinkedList &); //copy constructor ~LinkedList (); //destructor voidprintList() const; voidaddToBeginning(int n); voiddeleteList (); node * createClone () const; //generates the clone of the list and return the clone's head };
Deep Copy – How? LinkedList::LinkedList (constLinkedList & copy) { head = copy.createClone(); size = copy.size; } //generates a clone of the linked list object by generating new copies of //each node and connecting them as in the original. //Returns the head of the clone list. node * LinkedList::createClone () const { if (head == NULL) //if list is empty return NULL; //clone is empty as well //first generate the first clone node and connect to head of clone node * headClone = new node (head->info, NULL); node * ptr = head->next; //second node in orig. node * ptrClone = headClone; //to track the clone list while (ptr != NULL) { ptrClone->next = newnode (ptr->info, NULL); ptr = ptr->next; ptrClone = ptrClone->next; } returnheadClone; } Let'stracethis on the board for a samplecase
IntroducingLinkStringSet Class LinkedStringSet is a Tapestry Class for a linked list with string container. It works in set manner; that is, the insertion function first check if an element exists in the list. If so,it does not add. If not, adds. The implementation uses dummy node. The dummy node is the first node of the list and it does not contain real data. The first real node is the next of dummy node. The use of dummy node makes sure that the list is never empty (head does not point to NULL) The Iterator class of LinkStringSet is more or less similar to the other one that we have seen, but there are some differences due to the design of LinkStringSet. We will see some of them now; and some later. list object myFirst mySize dummy ab c NULL 3
LinkStringSet.h classLinkStringSet { public: // constructors . . . // accessors int size() const; // # elements in set . . . // mutators . . . private: structNode//nodedefinition is embedded in class { stringinfo; Node * next; . . . }; . . . Node * myFirst; //head of thelist intmySize; //number of elements in thelist };
Deep Copy – ForLinkedStringSet Class AnotherexampleusingLinkedStringSetclass of Tapestry LinkedStringSet list2(list1); //usageexample LinkStringSet::LinkStringSet (const LinkStringSet& set) :myFirst(new Node("header",set.clone())),//same as: myFirst = new Node("header",set.clone())); mySize(set.size())//seethecolorillustrationbelow { // deep copymade in an initializer list – no codeleftforhere } • The newly created object is calling a helperfunctionclone() • Shown in thenext slide list2.myFirst34 5 list1.myFirst NULL “header” 3 4 5 ………. NULL
Deep Copy – ForLinkedStringSet Class LinkStringSet::Node * LinkStringSet::clone() const { Node dummy ("header",NULL); Node * dest = &dummy; Node * temp = myFirst->next; while (temp != NULL) { dest->next = new Node(temp->info,NULL); dest= dest->next; temp = temp->next; } return dummy.next; } Note here that the dummy node is a normal variable on the run-time stack and thus its scope ends when we leave this function. However, due to the use in linkstringset class, the real dummy node is created outside • see prev. slide: myFirst(new Node("header",set.clone()))
Overloading in C++ Overloading is the practice of supplying more than one definition for a given function name. The compiler picks the appropriate version of the function or operator based on the arguments with which it is called. In case of an ambiguity, syntax error occurs Both free and member functions can be overloaded double max( double d1, double d2 ) { if ( d1 > d2 ) return d1; else return d2; } int max( int i1, int i2 ) { if ( i1 > i2 ) return i1; else return i2; } int main() { int i = max( 12, 8 ); //calls second one double d = max( 17.4, 32.9 ); //calls first one }
WritingFunctionforOperatorsandOperatorOverloading In C++, you can write functions for operators as well. When such an operator is used with its operands, that function is called. We will see syntax and example in the coming slides. You can overload operators as you can overload functions. Actually overloading for operators is inevitable since the defined operators of C++ already have other meanings Compiler differentiates among different meanings of the same operator using the types of the operands
Assignment Operator Let’s overload (i.e. make it work for your own class) the assignment operator. Usage:list2 = list1; Compiler interprets this as:list2.operator = (list1); Syntax for function header for operators Return_Type classname::operator Operator_Symbol(parameters) Syntax Examples for function headers that define operators: const myclass & myclass ::operator= (const myclass & rhs) const linkedlist & linkedlist::operator = (const linkedlist & list1) We use only one parameter for the assignment operator and this is for the rhs. Return type is always of the same class const-reference. Caution: Use const-reference parameters instead of value parameters to avoid unexpected tricks of shallow copy constructors and destructors
Implementation of Assignment Operator Similar to the deep copy constructor, but this is not a constructor. The assignment operator is called to reinitialize an object that has already been constructed. Since the object already exists, more bookkeeping is necessary. We will see the implementation of assignment operator next, but before that we will see this keyword and reference return types.
this When you apply a member function to an object • say calling member function func on object obj obj.func() the program invisibly executes this = & obj; before the function starts. Thus, • this is a pointer to the object on which the function is being executed (in our example obj). • therefore, the function can use *thisto refer to the current object on which the function is executing
Using & in returnvalues(referencereturntype) – Hortonpp. 219- 222 • Sometimes we use reference return values such as const ClassName & FunctionName (Parameters) ClassName &FunctionName (Parameters) • For example, operator += or ClockTime class and operator = LinkedList class, Operator << of ClockTime class (will see all later) • What is the difference between these and using just the ClassName as the return type (valuereturn)? ClassNameFunctionName (Parameters) • Using just the class name (value return) generates a copy of the return value when returning • This invokes copy constructor (user defined or, if it does not exist, default one) • This is risky if not implemented (shallow copy problem) • And it may be inefficient to call the copy constructor for this purpose, if you are returning an existing object
Using & in returnvalues(referencereturntype) • Sometimes the object to be returned has already been defined outside of the function (or dynamically allocated within the function) • In such a case, we can directly return that object without a copy operation • This is called reference return • The mechanisms with & does this. • Be careful, do not return a local variable using this method • Local variable gets lost after the function • Probably your program crashes • What is the effect of using const here? • Not much; actually a very defensive programming technique • If const is used, the compiler does not allow you to change function's returning value • Let's see this on an example (see refreturn.cpp)
Implementation of Assignment Operator • Prototypeaddedtotheclassdeclaration (LinkedListExtraOper.h) classLinkedList { private: node * head; int size; public: LinkedList (); LinkedList (const LinkedList &); //copy constructor ~LinkedList (); //destructor voidprintList() const; voidaddToBeginning(int n); voiddeleteList (); constLinkedList & LinkedList::operator = (constLinkedList & rhs); node * createClone () const; };
Implementation of Assignment Operator • Operatorfunction is defined in theclassimplementation file (LinkedListExtraOper.cpp in ourcase) • Whencalled as a = b; • a (thelefthandside – lhs) is theobject on whichthefunction is running (i.e. *this) • b is theparameter (i.e. rhsin ourexample) constLinkedList & LinkedList::operator = (constLinkedList & rhs) { if (this != &rhs) { deleteList(); head = rhs.createClone(); size = rhs.size; } return *this; } If not self assignment- weneedthisguard since weclearthelhsbeforetheassignment. Ifwedon'thavethis, in case of self assignment(e.g. a = a), thecontent is deletedbeforecopying. We do not want this. Deletethelhs– new data is coming; soold data shouldgo Makedeepcopyandstore in thelhs Allassighmentsshouldreturnlhsduetocascadedassignmentssuch as a = b = c = d
Demo Program (LinkedListExtraOper.cpp) LinkedList list1, list3; for (int k=0; k < 4; k++) { list1.addToBeginning(k+1); } cout << "list1 contains:\n"; list1.printList(); LinkedList list2(list1); cout << "list2 is created from list1 using copy constructor\n"; list3 = list1; cout << "list1 is assigned to list3\n"; list1.deleteList(); cout << "list1 is deleted\n"; cout << "\nlist2 contains:\n"; list2.printList(); cout << "\nlist3 contains:\n"; list3.printList(); list1.addToBeginning(100); list1.addToBeginning(50); cout << "list1 is reinitialized and contains:\n"; list1.printList(); list2 = list1; // same as list2.operator = (list1); cout << "list1 is assigned to list2\n"; cout << "list2 contains:\n"; list2.printList(); cout << "list2 is assigned to itself\n"; cout << "list2 contains:\n"; list2 = list2; //try this also after deletingif (this != &rhs) at operator definition list2.printList(); Let's run this
Assignment Operator:Implementation in LinkStringSetclass const LinkStringSet& LinkStringSet::operator = (const LinkStringSet& set) { if (&set != this) //to prevent misbehaviour if a=a is used { reclaimNodes(myFirst->next); //free memory of lhs myFirst->next = set.clone(); //copy rhs mySize = set.size(); } return *this; //return lhs for situations when //a = b = c; is used }//which is equal to the statement: //a = (b = c)
Linksetdemo.cpp int main() { LinkStringSet a,b; a.insert("apple"); a.insert("cherry"); cout << "a : "; Print(a); //cherry apple 2 b = a; cout << "b : "; Print(b); //cherry apple 2 a.clear(); cout << "a : "; Print(a); // 0 cout << "b : "; Print(b); //cherry apple 2 //as intended with =, provided by deepcopy in linkstringset.cpp return 0; } Check out LinkStringSet.h, cpp and this demo program
TapestryChp. 9.4clockt.h andclockt.cpp We will now look at the Tapestry’s ClockTime class for: • more operator overloading • class design How to design a clock time class? • to represent a clock time object: for example 19:59:00
ClockTimeClass class ClockTime { public: ClockTime(); ClockTime(int secs, int mins, int hours); int Hours() const; // returns # hours int Minutes() const; // returns # minutes int Seconds() const; // returns # seconds string tostring() const; // converts to string bool Equals(const ClockTime& ct) const; // true if == ct bool Less (const ClockTime& ct) const; // true if < ct const ClockTime & operator +=(const ClockTime & ct); private: void Normalize(); //normalized such that < 60 secs, < 60 min int mySeconds; // constrained: 0-59 int myMinutes; // constrained: 0-59 int myHours; };
ClockTimeClass ClockTime::ClockTime(int secs, int mins, int hours) : mySeconds(secs), myMinutes(mins), myHours(hours) // postcondition: all data fields initialized { Normalize(); } void ClockTime::Normalize() { myMinutes += ... mySeconds ... myHours += ... myMinutes ... }
ClockTimeClass ClockTime::ClockTime(int secs, int mins, int hours) : mySeconds(secs), myMinutes(mins), myHours(hours) // postcondition: all data fields initialized { Normalize(); } void ClockTime::Normalize() { myMinutes += mySeconds/60; // overflow from secs to myMinutes mySeconds %= 60; // now between 0 and 59 myHours += myMinutes/60; // overflow from myMinutes to myHours myMinutes %= 60; // now between 0 and 59 }
HelperFunctions of theClockTimeclass These will be used in operator overloading. They implement the straightforward meaning (make sure you understand): bool ClockTime::Equals(const ClockTime& c) const//usage: c1.Equals(c2) // postcondition: returns true if this object’s time == c { return ( Hours() == c.Hours() && Minutes() == c.Minutes() && Seconds() == c.Seconds() ); }
HelperFunctions of theClockTimeclass These will be used in operator overloading. They implement the straightforward meaning (make sure you understand): bool ClockTime::Less(const ClockTime& c) const // postcondition: returns true if this object’s time < c { return ( Hours() < c.Hours() ) || ( ( Hours() == c.Hours() ) && ( ( Minutes() < c.Minutes() ) || ( ( Minutes() == c.Minutes() ) && ( Seconds() < c.Seconds() ) ) ) ); }
OverloadingOperators Now let’s overload the operator >=. First notice that we should be able to use the “>=“ operator with clocktimes in 3 ways: ClockTime c1, c2; if ( c1 >= c2) if ( c1 >= 1) //let this mean "c1 is more than or equal to 1 hr" if ( 1 >= c1) ...
Overloading –complete case for >= Let's detail a bit ClockTime c1, c2; if ( c1 >= c2) can be also called as c1.operator>=(c2) bool ClockTime::operator>=(const ClockTime & rhs) { return ! ( Less(rhs) ); //uses the helper function Less() of c1. } if ( c1 >= 1) can be also called as c1.operator>=(1) bool ClockTime::operator>=(const int & rhs) { return ( Hours() >= rhs ); } //uses the accessor function Hours() of c1. //alternative: { return ( myHours >= rhs ); } //since the member functions can access private data if ( 1 >= c1) cannot call 1.operator>=(c1) since 1 is a constant, so this version of the operator>= must be a free function
Overloading –complete case for >= if ( c1 >= c2) can be also called as c1.operator>=(c2) boolClockTime::operator>=(constClockTime & rhs) { return ! ( Less(rhs) ); //usesthehelperfunctionLess() of c1. } if ( c1 >= 1) can be also called as c1.operator>=(1) boolClockTime::operator>=(constint & rhs) { return ( Hours() >= rhs ); } //usestheaccessorfunctionHours() of c1. //alternative: { return ( myHours >= rhs ); } //since thememberfunctions can accessprivatedata if ( 1 >= c1) cannotcall1.operator>=(c1) since 1 is a constant, sothisversion of theoperator>= must be a freefunction booloperator >= (constint & lhs, constClockTime & rhs) { ClockTimetemp(0, 0, lhs); returntemp >= rhs; }
Operator += Usage: Interpreted as: c1 += ct; c1.operator+=(ct); const ClockTime & ClockTime::operator += (const ClockTime & ct) // postcondition: add ct, return normalized result { }
Operator += Usage: Interpreted as: c1 += ct; c1.operator+=(ct); const ClockTime & ClockTime::operator += (const ClockTime & ct) // postcondition: add ct, return normalized result { mySeconds += ct.mySeconds; myMinutes += ct.myMinutes; myHours += ct.myHours; Normalize(); return *this; }
Operator += why operator += returns *this? • same arguments as for the assignment operator (=) • cascaded assignments • in case someone uses: e.g. c1 = c2 += c3; or c1 += c2 += c3 Remember assignment operators' associativity is from right to left e.g. c1 = c2 = c3 means c1 = (c2 = c3) so c2 = c3 should return c2 so that it can be assigned to c1 Similarly c1 += c2 += c3 means c1 += (c2 += c3) • This is a bit cryptic (do not use statements like this), but in general, one should design the same behavior as it is fact that such a usage exists and legal for all types.
clockt.cpp – otheroverloadedoperators Inclocktcpp, onlysomeforms of theoverloadedoperatorsareprovidedandmostaredeclared as freefunctions: ostream & operator <<(ostream & os, constClockTime & ct); istream & operator >>(istream & is, ClockTime & ct); ClockTimeoperator +(constClockTime & lhs, constClockTime & rhs); bool operator ==(constClockTime& lhs, constClockTime& rhs); bool operator !=(constClockTime& lhs, constClockTime& rhs); bool operator <(constClockTime& lhs, constClockTime& rhs); bool operator >(constClockTime& lhs, constClockTime& rhs); bool operator <=(constClockTime& lhs, constClockTime& rhs); bool operator >=(constClockTime& lhs, constClockTime& rhs); However, most of theseparticularfunctions can - orevenshould be - memberfunctions • since theyhave a left-hand-sidewhich is a clocktimeobject, they can be memberfunctions (which is better) • Theonlyexceptions here arethe << and >> operatorswhichhaveto be freefunctions since lhs is not a clocktimeobject Whenwearewritingversions of theoverloadedoperatorswhich has a type other than that class on theleft-hand-side, theyhaveto be freefunctions