advanced issues on classes and oop part 1 - constructor, destructor, copy constructor, deep copy,...

57
Advanced Issues on Advanced Issues on Classes and OOP Classes and OOP Part 1 - constructor, destructor, copy constructor, deep copy, assignment operat or -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

Upload: hilary-lane

Post on 26-Dec-2015

259 views

Category:

Documents


6 download

TRANSCRIPT

Advanced Issues on Classes and OOPAdvanced Issues on Classes and OOPPart 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)

2

ConstructorConstructorConstructors are special member fuctions of a class.

When an object of a class is created, C++ calls the constructor for that class.

Properties:– Constructors have the same name as the class. – Constructors do not return any values – Constructors are invoked first when a class 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

Default ConstructorDefault ConstructorConstructor with no parameter.

Node::Node(){

. . . //initialization code}

A constructor with parameters can also be default if the default values of these parameters are provided at declaration. In this case, if no argument is provided when the constructor is called, default values are used. If argument(s) is/are provided, given arguments are used during construction.Node::Node(int num = 0, Node * ptr = NULL){

. . . //initialization code}

Node myNode();Node myOtherNode (10);Node * ptr = new Node();Node * ptr2 = new Node(10, ptr);Node * ptr3 = new Node (100);

3

Default ConstructorDefault ConstructorWhat 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)

4

5

Constructor ExampleConstructor Example//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;

}

Instead of these two constructors you can have

Point::Point(int xx=0, int yy=0) { x = xx; y = yy;}

If no constructor was defined, then a created object would have two integers x and y with no initial values

Using Initializer List in ConstructorUsing Initializer List in ConstructorSyntax

ConstructorHeader : privatedata (value), privatedata(value), . . . {

// other constructor code comes here, executed after assigning values in the initializer list

}Examples

6

// default constructorPoint::Point() : x(0), y(0) { }

//Alternative default constructor implementationPoint::Point() : x(0) {

y = 0;}

// constructor with parametersPoint::Point(int xx, int yy) : x(xx), y(yy){ }

7

DestructorDestructor Tapestry pp. 624-625Tapestry pp. 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– 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

};

8

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

}}

9

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

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

10

DestructorDestructorint 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 set when this function returns?

At the end of the function, destructor of LinkStringSet class is called on set since set goes out of scope.

11

DestructorDestructor

Since the compiler makes this call automatically, it creates a 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 all memory)

– if the programmer has supplied one, compiler won’t generate a dummy destructor

• Remember that the (real) destructor should free all memory taken with “new” or other dynamic memory allocation functions.

Let's see linkedlistextra class and demo (linkedlistextra.cpp and linkedlistextrademo.cpp)

for live examples

12

Copy Constructor, Operator Overloading, Assignment Copy Constructor, Operator Overloading, Assignment Overview and PurposeOverview and Purpose

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 list2list3 = list1 + list2; //addition of lists, with suitable meaninglinkedlist list4(list1); //construct list4 from list1

First two are typically operators that you take for granted for built-in types. Now you are asked to give the same functionality (as long as meaningful for your

class).

Copy ConstructorCopy ConstructorSpecial constructor called when an object is first declared and initialized from

another object of the same type

Example:LinkedList list3(list); //list is previously defined

Date today;Date tomorrow (today+1);Date yesterday (today-1);

Date 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 ConstructorCopy Constructor for for LinkedListLinkedList Class ClassYou have to add a prototype to the class definition as well.Caution: Always use const reference parameter here.

class LinkedList {

private:node * head;int size;

public: LinkedList (); LinkedList (const LinkedList &); //copy constructor ~LinkedList (); //destructor void printList() const; void addToBeginning(int n); // more functions here

};

LinkedList::LinkedList (const LinkedList & copy){

head = copy.head;size = copy.size;

}

Copy ConstructorCopy ConstructorThe copy constructor in the previous slide makes "shallow copy" ; only the

private data of a class instance is copied. - In the case of a linked list, this would be the head pointer, NOT the whole

list- The new list share the same memory as the old one!

Let's give an example

Suppose list1 is like the

one on the right.

After LinkedList list2(list1); the lists become as follows

15

list1 3 4 5

list2 node * head

int size = 3

node * head

int size = 3

list1 3 4 5node * head

int size = 3

Copy ConstructorCopy ConstructorFor every class, there is a default 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) 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 always better to use const reference parameters instead of value parameters for class types. In value parameter passing, implicitly

copy constructor is invoked. Using const reference parameter avoids this and unexpected effects of default/shallow copy constructors and

destructors.

Why Shallow Copy is Bad?Why Shallow Copy 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.

Good Good Copy ConstructorCopy Constructor – Deep Copy – Deep CopyIf 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 5node * head

int size = 3

node * head

int size = 3

Deep CopyDeep Copy – How? – How?No change in the class definition. Only implementation is different

- See this and next slide

- Also see linkedlistextra class and demo (linkedlistextra.cpp)

class LinkedList {

private:node * head;int size;

public: LinkedList (); LinkedList (const LinkedList &); //copy constructor ~LinkedList (); //destructor void printList() const; void addToBeginning(int n); void deleteList (); node * createClone () const;

//generates the clone of the list and return the clone's head

};

Deep CopyDeep Copy – How? – How?LinkedList::LinkedList (const LinkedList & 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 emptyreturn NULL; //clone is empty as well

//first generate the first clone node and connect to head of clonenode * 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 = new node (ptr->info, NULL);ptr = ptr->next;ptrClone = ptrClone->next;

}return headClone;

}

Let's trace this on the board for a sample case

Deep CopyDeep Copy – How? – How? Another example using LinkedStringSet class of Tapestry

LinkedStringSet list2(list1); //usage example

LinkStringSet::LinkStringSet (const LinkStringSet& set) : myFirst(new Node("header",set.clone())), //same as: myFirst = new Node("header",set.clone()));

mySize(set.size()) //see the color illustration below

{

// deep copy made in an initializer list – no code left for here

}

- The newly created object is calling a helper function clone()- Shown in the next slide

list2.myFirst 3 4 5

list1.myFirst ………. 3 4 5

“header” NULL

NULL

Deep CopyDeep Copy – How? – How?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 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()))

Operator OverloadingOperator Overloading

Overloading in C++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;

elsereturn d2;

}

int max( int i1, int i2 ){

if ( i1 > i2 )return i1;

elsereturn i2;

}

int main(){ int i = max( 12, 8 ); //calls second one double d = max( 17.4, 32.9 ); //calls first one}

Writing Function for Operators and Writing Function for Operators and Operator OverloadingOperator Overloading

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 OperatorAssignment OperatorLet’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)

Caution: Use const-reference parameters instead of value parameters to avoid unexpected tricks of shallow copy constructors and destructors

Implementation of Implementation of Assignment OperatorAssignment OperatorSimilar to the deep copy constructor, but 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.

28

thisthisWhen 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 to execute. Thus,– this is a pointer to the object on which the function is being

executed (in our example obj).– therefore, the function can use *this to refer to the current object

on which the function is executing

Implementation of Implementation of Assignment OperatorAssignment Operator• Prototype added to the class declaration (LinkedListExtraOper.h)

class LinkedList {

private:node * head;int size;

public: LinkedList (); LinkedList (const LinkedList &); //copy constructor ~LinkedList (); //destructor void printList() const; void addToBeginning(int n); void deleteList (); const LinkedList & LinkedList::operator = (const LinkedList

& rhs); node * createClone () const;

};

Implementation of Implementation of Assignment OperatorAssignment Operator• Operator function is defined in the class implementation file

(LinkedListExtraOper.cpp in our case)• When called as a = b;

– a (the left hand side – lhs) is the object on which the function is running (i.e. *this)– b is the parameter (i.e. rhs in our example)

const LinkedList & LinkedList::operator = (const LinkedList & rhs){

if (this != &rhs){

deleteList(); head = rhs.createClone();size = rhs.size;

}return *this;

}

If not self assignment - we need this guard since we clear the lhs before the assignment. If we don't have this, in case of self assignment (e.g. a = a), the content is deleted before copying.

Delete the lhs – new data is coming; so old data should go

Make deep copy and store in the lhs

All assighments should return lhs due to cascaded assignments such as a = b = c

Demo Program (LinkedListExtraOper.cpp) 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 deleting if (this != &rhs) at operator definitionlist2.printList();

Let's run this

Assignment OperatorAssignment Operator:: Implementation in Implementation in LinkStringSet classLinkStringSet class

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.cppLinksetdemo.cppint 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

Tapestry Chp. 9.4Tapestry Chp. 9.4clockt.h and clockt.cppclockt.h and clockt.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: 19:59:00

ClockTime ClassClockTime Classclass 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;};

ClockTime ClassClockTime ClassClockTime::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 ...}

ClockTime ClassClockTime ClassClockTime::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

}

Helper Functions of the ClockTime classHelper Functions of the ClockTime classThese 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() );}

Helper Functions of the ClockTime classHelper Functions of the ClockTime classThese 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() ) )

)

);

}

Overloading OperatorsOverloading Operators

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 >=Overloading –complete case for >= Let's detail a bit

ClockTime c1, c2;

if ( c1 >= c2) calls c1.operator>=(c2) bool ClockTime::operator>=(const ClockTime & rhs)

{ return ! ( Less(rhs) ); //uses the helper function Less() of c1.

}

if ( c1 >= 1) calls c1.operator>=(1) : let this mean "is the time more than 1 hr"

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 >=Overloading –complete case for >=if ( c1 >= c2) calls c1.operator>=(c2)

bool ClockTime::operator>=(const ClockTime & rhs){ return ! ( Less(rhs) ); //uses the helper function Less() of c1.

}

if ( c1 >= 1) calls c1.operator>=(1) : let this mean "is the time more than 1 hr"

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

bool operator>=(const int & lhs, const ClockTime & rhs)

{ ClockTime temp(0, 0, lhs);

return temp >= rhs;}

Operator +=Operator +=

Usage: Interpreted as: c1 += ct; c1.operator+=(ct);

const ClockTime & ClockTime::operator += (const ClockTime & ct)// postcondition: add ct, return normalized result{

}

Operator +=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 +=Operator +=why operator += returns *this?

– same arguments as for the assignment operator (=)• 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 exists for built-in types (it is legal to say this for ints and other built-in types)

clockt.cpp – other overloaded operatorsclockt.cpp – other overloaded operatorsIn clockt cpp, only some forms of the overloaded operators are provided (apparently

they did not see a good use comparing clocktime objects to constants etc.) and most are declared as free functions:

– ostream & operator << (ostream & os, const ClockTime & ct);

– ClockTime operator + (const ClockTime & lhs, const ClockTime & rhs);

– bool operator == (const ClockTime& lhs, const ClockTime& rhs);– bool operator != (const ClockTime& lhs, const ClockTime& rhs);– bool operator < (const ClockTime& lhs, const ClockTime& rhs);– bool operator > (const ClockTime& lhs, const ClockTime& rhs);– bool operator <= (const ClockTime& lhs, const ClockTime& rhs);– bool operator >= (const ClockTime& lhs, const ClockTime& rhs);

However, most of these particular functions can - or even should be - member functions – since they have a left-hand-side which is a clocktime object, they can be

member functions (which is better)– The only exception here is the << operator which has to be a free function

since lhs is not a clocktime objectWhen we are writing versions of the overloaded operators which has constants on the

left-hand-side, they have to be free functions

Operator +Operator +

Usage: Interpreted as:c = c1 + c2; c = (operator+(c1,c2));

operator+ is a free function

How can you simply implement it? Hint: using the previously defined operator+= of the class:

ClockTime operator+ (const ClockTime & lhs, const ClockTime & rhs)// postcondition: return lhs + rhs (normalized for myMinutes, mySeconds){ .............................. .............................. .............................}

Operator +Operator +The free function operator+ can be implemented using the previously defined operator+= class:

ClockTime operator+ (const ClockTime & lhs, const ClockTime & rhs)

// postcondition: return lhs + rhs (normalized for myMinutes, mySeconds)

{

ClockTime result(lhs); //uses the default (compiler generated) copy constructor

result += rhs; //uses the previously defined operator+=

return result;

}

Why is the return type just ClockTime but not a reference as in = and +=?– We return result, which is a local object. Returning reference of a local object/variable is very

problematic since the scope ends after the function. In the current form of ClockTime class this may not cause a problem but if we had a destructor for ClockTime, that would be a problem. Let's simulate this case by having a destructor that initializes private data members.

– When we return value, copy constructor is implicitly called, a copy of the returned object is created and this is returned to the caller function.After using it at the caller function, the copy constructed value is destructed. This is exactly what we want.

Why did we need result? Why not simply have only return lhs+= rhs?– Because you cannot change the value of a const reference parameter, compiler does not allow this.

What about using value parameters for lhs and rhs? – This time you could just have return lhs+= rhs; in the function body; but causes unnecessary

copy

49

Friend functionsFriend functionsA friend function is used for accessing the non-public members of a class.

• A friend function is an ordinary (free) function or a member function of another class.

• The friend function is written as any other normal function.

• However, you have to add a prototype for the function in the class declaration by putting the keyword friend at the beginning

– Use friend keyword in the prototype only, not in the function definition.

• It is possible to declare a function as friend in any number of classes.

• When a class is declared as a friend, the friend class has access to the private data of the class that made this a friend.

– Will see friend classes later

• The function is invoked without the use of an object: objects are passed to it as arguments.

An Example Friend FunctionAn Example Friend FunctionLet's overload + operator (two operands but the first one is an integer,

second one is a clock)

ClockTime operator + (int lhs, const ClockTime& rhs)

Adds lhs hours to rhs and returns the new clock value.

Let's implement this as a free function. Actually ClockTime class has enough accessors for implementation, but let's do it by accessing private data members. We add the following function to clockt.cpp

ClockTime operator + (int lhs, const ClockTime& rhs){

ClockTime temp (rhs.mySeconds, rhs.myMinutes, rhs.myHours + lhs);return temp;

}

Since we use private data members of the class, this function should be a friend function. To do so also add the following prototype to clocktime.h within the class declaration

friend ClockTime operator + (int lhs, const ClockTime& rhs);

Overloading I/O operatorsOverloading I/O operatorsostream & operator << (ostream & os, const ClockTime & ct)// postcondition: inserts ct onto os, returns os

// format is h:m:s { os << ct.tostring(); return os;}

string ClockTime::tostring() const{ ostringstream os; //to use a string as a stream os.fill('0'); //unused spaces on the left fill with 0s os << Hours() << ":" << setw(2) << Minutes() << ":"

<< setw(2) << Seconds(); 1:09:59 return os.str();}

Overloading I/O operatorsOverloading I/O operatorsostream & operator << (ostream & os, const ClockTime & ct)// postcondition: inserts ct onto os, returns os

// format is h:m:s {

os << ct.tostring(); return os;

}

• operator << cannot be a member function of the class (in general) since lhs is a stream cout << c1;

• ostream (output streams) has to be passed as reference Since the ostream is modified when you put something on the stream

• operator << must return osin case we use: cout << c1 << c2; since << is left-to-right associative, the

result of cout << c1 must be cout (parameter os).

Overloading I/O operatorsOverloading I/O operators

• operator >> is to read an object from an inpuıt stream (input file stream, input string stream or just cin)

• operator >> cannot be a member function of the class since lhs is a stream cin >> c1;This operator function has been declared as friend of ClockTime class since we need to update private data members of ClockTime and do not have mutators for this. However, normally it'd be better to have mutators and change the private part via mutators.

• istream (input streams) has to be passed as reference Since the istream is modified when you read something from the stream

• operator >> must return is in case we use: cin >> c1 >> c2; since >> is left-to-right associative, the result of cin >> c1 must be cin (parameter is).

istream & operator >> (istream & is, ClockTime & ct)//postcondition: reads private members of ct from is, and normalize ct// returns is{

is >> ct.myHours >> ct.myMinutes >> ct.mySeconds;ct.Normalize();return is;

}

Other overloaded operatorsOther overloaded operatorsbool operator == (const ClockTime& lhs, const ClockTime& rhs)

{

return lhs.Equals(rhs);

}

bool operator != (const ClockTime& lhs, const ClockTime& rhs)

{

return ! (lhs == rhs);

}

... Let's see all of these in clockt.h and clockt.cpp

What happened to operator = ?What happened to operator = ?Have you realized that ClockTime class do not have operator = ?

– Neither member nor free function

But we can use assignment operator (see useclock.cpp)

If there is no = operator implemented in overloaded operators compiler provides a default implementation automatically

Default assignment operator makes shallow copy as in default copy constructor– Only private data members are copied

If you are happy with that, then you do not need to write a function for =– This is acceptable for ClockTime class since it does not use any

dynamic memory from heap

Using & in return valuesUsing & in return values(reference return type) – (reference return type) – Horton pp. 219- 222Horton pp. 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

• What is the difference between these and using just the ClassName as the return type (value return)?

ClassName FunctionName (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 is ineffient to call the copy constructor for this purpose56

Using & in return valuesUsing & in return values(reference return type)(reference return type)

• 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, 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)

57