You are on page 1of 56

Advanced Issues on Classes

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)

- operator overloading
-Horton pp.446... (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 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
at the constructor.
Constructors may or may not take parameters
A constructor with no parameter is called default constructor

Default Constructor
Constructor 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
Node
Node
Node
Node

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

Default Constructor
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 a 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)

Constructor Example
//point.h
// default constructor
#ifndef POINT_H
Point::Point() {
#define POINT_H
x = 0;
class Point {
y = 0;
public:
}
Point(); //default constructor
Point(int xx, int yy);
// constructor with parameters
// constructor with parameters
Point::Point(int xx, int yy) {
. . .
x = xx;
private:
y = yy;
int x;
}
int y;
Instead of these two constructors you can have
};
#endif
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 Constructor


Syntax
ConstructorHeader :
privatedata (value), privatedata(value), . . .
{
// other constructor code comes here, executed after assigning values in the initializer list

}
Examples

// default constructor
Point::Point() : x(0), y(0)
{
}
//Alternative default constructor implementation
Point::Point() : x(0)
{
y = 0;
}
// constructor with parameters
Point::Point(int xx, int yy) : x(xx), y(yy)
{
}
6

Destructor
Tapestry pp. 624-625
Each class should also have a destructor which should take care of
returning any unreturned memory.
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
};

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;
}
}
8

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
If you do not provide a destructor for your class, the compiler provides one, but
thats 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
9

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

10

Destructor
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. doesnt free all
memory)

if the programmer has supplied one, compiler wont generate a


dummy destructor
Remember that the (real) destructor should free all memory taken with
new:

Let's see linkedlistextra class and demo


(linkedlistextra.cpp) for live examples
11

Copy Constructor, Operator Overloading, Assignment


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

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

Copy Constructor
Special constructor called when an object is first declared and initialized from
another object of the same type
Example:
LinkedList list3(list);
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 Constructor for LinkedList Class


You 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 &);
~LinkedList ();
//destructor
void printList() const;
void addToBeginning(int n);
// more functions here
};

//copy constructor

LinkedList::LinkedList (const LinkedList & copy)


{
head = copy.head;
size = copy.size;
}

Copy Constructor
The 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


list1 node * head
3
4
Suppose list1 is like the
int size = 3
one on the right.
After LinkedList list2(list1); the lists become as follows
list1

node * head
int size = 3

list2

node * head

int size = 3

15

Copy Constructor
For 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, size for
linked lists) from one object to the other.
The entire list is not copied.

Shallow copy may not 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?


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.

Good Copy Constructor Deep Copy


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

node * head

int size = 3

list2

node * head
int size = 3

Deep Copy 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 Copy 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 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 = new node (ptr->info, NULL);
ptr = ptr->next;
Let's trace this
ptrClone = ptrClone->next;
}
on the board for
return headClone;

a sample case

Deep Copy How?


An other example using LinkedStringSet class of Tapestry
LinkedStringSet list2(list1);
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 called clone()


- Shown in the next slide
list2.myFirst

header

list1.myFirst

Deep Copy How?


LinkStringSet::Node * LinkStringSet::clone() const
{
Node dummy ("header",0);
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 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 Overloading

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
}

Writing Function for Operators and


Operator 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 Operator
Lets overload (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 Assignment Operator


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

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

28

Implementation of Assignment 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 Assignment Operator

Operator function is defined in the cpp file (LinkedListExtraOper.cpp)


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)

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


{
if (this != &rhs)
If not self assignment {
we need this guard since
deleteList();
we clear the lhs before
head = rhs.createClone();
the assignment. If we
Delete the lhs
size = rhs.size;
don't have this, in case of
new data is
}
self assignment (e.g. a =
Make
coming, old
return *this;
a), the content is deleted
deep
data should go
}
before copying.
copy and
All assighments should
store in
return lhs due to
the lhs
cascaded assignments
such as a = b = c

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);
list3 = list1;

Let's run this

cout << "list2 is created from list1 using copy constructor\n";

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);
list1.printList();

cout << "list1 is reinitialized and contains:\n";

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 definition
list2.printList();

Assignment Operator: Implementation in


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

Tapestry Chp. 9.4


clockt.h and clockt.cpp
We will now look at the Tapestrys 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 Class
class ClockTime
{
public:
ClockTime();
ClockTime(int secs, int mins, int hours);
int Hours()
const;
int Minutes()
const;
int Seconds() const;
string tostring() const;

// returns # hours
// returns # minutes
// returns # seconds
// 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();
int mySeconds;
int myMinutes;
int myHours;
};

//normalized such that < 60 secs, < 60 min


// constrained: 0-59
// constrained: 0-59

ClockTime Class
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 ...
}

ClockTime Class
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;
mySeconds %= 60;
myHours
myMinutes
}

+= myMinutes/60;
%= 60;

// overflow from secs to myMinutes


// now between 0 and 59
// overflow from myMinutes to myHours
// now between 0 and 59

Helper Functions of the ClockTime class


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 objects time == c
{
return ( Hours() == c.Hours() &&
Minutes() == c.Minutes() &&
Seconds() == c.Seconds() );
}

Helper Functions of the ClockTime class


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 objects time < c
{
return ( Hours() < c.Hours() ) ||
( ( Hours() == c.Hours() ) &&
( ( Minutes() < c.Minutes() ) ||
( ( Minutes() == c.Minutes() ) && ( Seconds() < c.Seconds() ) )
)
);
}

Overloading Operators
Now lets 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) 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
of the operator>= must be a free function

this version

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
of the operator>= must be a free function
bool operator>=(const int & lhs, const ClockTime & rhs)
{ ClockTime temp(0, 0, lhs);
return temp >= rhs;
}

this version

Operator +=
Usage:
c1 += ct;

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

const ClockTime & ClockTime::operator += (const ClockTime & ct)


// postcondition: add ct, return normalized result
{

Operator +=
Usage:
c1 += ct;

Interpreted as:
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 (=)
in case someone uses: e.g.

c1 = c2 += c3;
c1 += c2 += c3

or

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 operators


In 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
bool
bool
bool
bool
bool

operator ==
operator !=
operator <
operator >
operator <=
operator >=

(const ClockTime& lhs, const ClockTime& rhs);


(const ClockTime& lhs, const ClockTime& rhs);
(const ClockTime& lhs, const ClockTime& rhs);
(const ClockTime& lhs, const ClockTime& rhs);
(const ClockTime& lhs, const ClockTime& rhs);
(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 object
When we are writing versions of the overloaded operators which has constants on the
left-hand-side, they have to be free functions

Operator +
Usage:
c = c1 + c2;

Interpreted as:
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 +
The free function operator+ can be implemented using the previously defined operator+= of the
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.

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

Friend functions
A 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.
49

An Example Friend Function


Let'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 operators


ostream & 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();
return os.str();
}

1:09:59

Overloading I/O operators


ostream & 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 (in general)


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 os


in case we use: cout << c1 << c2; since << is left-to-right associative, the
result of cout << c1 must be cout (parameter os).

Other overloaded operators


bool 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 = ?


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 values


(reference return type) Horton pp. 280- 283
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 purpose
55

Using & in return values


(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)
56

You might also like