Bit2203 Introduction To Threads and Processes Exceptions and Exception Handling Operatoroverloading
Bit2203 Introduction To Threads and Processes Exceptions and Exception Handling Operatoroverloading
DEPARTMENT OF INFORMATION
TECHNOLOGY.
Ochieng E. Owino
([email protected])
Prerequisite:
Course aims
To enable the learner to understand advanced object-oriented programming con-
cepts.
Learning outcomes
Upon completion of the course the students should be able to:
Instruction methodology
Lectures, practical and tutorial sessions in Computer Laboratory, individual and
group assignments, exercises and project work
ii
Reference Textbooks:
1. Duggal, Vijay (2009). CADD primer : a general guide to computer aided
design and drafting, ISBN: 0-9629165-2-8.
Course Journals
1. Acta Informatica ISSN 0001-5903.
Reference Journals
1. Journal of computer science and Technology ISSN 1000-9000.
iii
Assessment information
The module will be assessed as follows;
iv
Contents
2 Exceptions in C++ 11
2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.1.1 Exceptions and the throw and try..catch statements . . . . . 12
2.2 Exception specification . . . . . . . . . . . . . . . . . . . . . . . . 14
2.3 Standard exceptions . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
CONTENTS CONTENTS
vi
CONTENTS CONTENTS
8 Component-Based Developments 94
8.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8.2 The Software Crisis. . . . . . . . . . . . . . . . . . . . . . . . . . . 96
8.3 General design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8.3.1 Coupling & Cohesion . . . . . . . . . . . . . . . . . . . . 98
8.4 What is a Component . . . . . . . . . . . . . . . . . . . . . . . . . 99
8.4.1 Component Model . . . . . . . . . . . . . . . . . . . . . . 99
8.4.2 Component Framework . . . . . . . . . . . . . . . . . . . . 99
8.5 Life Cycle of Component Based Software Engineering . . . . . . . 100
8.5.1 Requirements Analysis Phase . . . . . . . . . . . . . . . . . 100
8.5.2 Architecture Selection Phase . . . . . . . . . . . . . . . . . 101
8.5.3 Component Identification Phase . . . . . . . . . . . . . . . 101
8.5.4 System Integration Phase . . . . . . . . . . . . . . . . . . . 101
8.5.5 Testing Phase . . . . . . . . . . . . . . . . . . . . . . . . . 102
8.5.6 Maintenance Phase . . . . . . . . . . . . . . . . . . . . . . 102
8.6 Current Component Technologies . . . . . . . . . . . . . . . . . . 103
8.7 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
vii
CONTENTS CONTENTS
viii
LESSON 1
Introduction to Threads and Processes
Learning outcomes
Upon completing this topic, you should be able to:
• Process state: everything that can affect, or be affected by, the process: code,
data, call stack, open files, network connections, etc.
• This is not called "uniprocessing": that refers to a system with only one pro-
cessor.
Dispatching
OS uses a process control block to keep track of each process:
• Scheduling information.
• Plan 1: Link together the ready threads into a queue. Dispatcher grabs first
thread from the queue. When threads become ready, insert at back of queue.
• Plan 2: give each thread a priority, organize the queue according to priority.
Or, perhaps have multiple queues, one for each priority class.
CPU can only be doing one thing at a time: if a thread is executing, dispatcher isn’t:
OS has lost control. How does OS regain control of processor?
Traps (events occurring in current thread that cause a change of control into the
operating system):
• System call.
• Page fault.
Interrupts (events occurring outside the current thread that cause a state switch into
the operating system)):
When thread isn’t running, its state must be saved in process control block. What
gets saved? Everything that next thread could damage:
• Program counter.
• Registers.
• Floating-point registers.
• Exec replaces memory with code and data from a given executable file.
Example:
int pid = fork();
if (pid == 0)
{ /* Child process */
exec("foo");
}
else{ /* Parent process */
waitpid(pid, &status, options);
}
Process creation in Windows:
CreateProcess combines fork and exec
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
PVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation );
• Ready - The process has all the resources available that it needs to run, but
the CPU is not currently working on this process’s instructions.
• Waiting - The process cannot run at the moment, because it is waiting for
some resource to become available or for some event to occur. For example
the process may be waiting for keyboard input, disk access request, inter-
process messages, a timer to go off, or a child process to finish.
1.4. Threads
A thread is an independent flow of control that operates within the same address
space as other independent flows of controls within a process.
Traditionally, thread and process characteristics are grouped into a single entity
called a process. In other operating systems like linux, threads are sometimes called
lightweight processes, Most modern operating systems also support threads: multi-
ple execution streams within a single process.
• Each thread has a separate stack for procedure calls (in shared memory).
• Manage I/O more efficiently: some threads wait for I/O while others compute.
• Stack.
Printf(“child complete”);
exit(0);
}
}
C program using a fork for separate processes.
Creating a separate process using the UNIX fork( ) system call.
CloseHandle(p.hProcess);
CloseHandle(p.hThread);
}
1.7. Summary
Process :- "An execution stream in the context of a particular process state."
• Stack.
A kernel thread is the schedulable entity, which means that the system scheduler
handles kernel threads.
Revision Questions
Problem. Using a diagram shows how CPU switches from process to process.
10
LESSON 2
Exceptions in C++
Learning outcomes
By the end of this topic you should be able to;
11
2.1. Introduction
2.1.1. Exceptions and the throw and try..catch statements
Exceptions allow passing information about some exception condition (usually an
error) by means of the throw statement to the catch clause that encloses it or any of
the calls on the call stack. The calls look identical, and the error processing code is
in the function that wants to catch it.
Exceptions provide a way to react to exceptional circumstances (like runtime errors)
in programs by transferring control to special functions called handlers.
To catch exceptions, a portion of code is placed under exception inspection. This is
done by enclosing that portion of code in a try-block. When an exceptional circum-
stance arises within that block, an exception is thrown that transfers the control to
the exception handler. If no exception is thrown, the code continues normally and
all handlers are ignored.
An exception is thrown by using the throw keyword from inside the try block.
Exception handlers are declared with the keyword catch, which must be placed
immediately after the try block:
// exceptions
#include <iostream>
using namespace std;
int main ()
{
try
{
throw 20;
}
catch (int e)
{
cout << "An exception occurred. Exception Nr. " << e << ’\n’;
}
return 0;
}
The code under exception handling is enclosed in a try block. In this example this
code simply throws an exception:
12
throw 20;
A throw expression accepts one parameter (in this case the integer value 20), which
is passed as an argument to the exception handler.
The exception handler is declared with the catch keyword immediately after the
closing brace of the try block. The syntax for catch is similar to a regular function
with one parameter. The type of this parameter is very important, since the type of
the argument passed by the throw expression is checked against it, and only in the
case they match, the exception is caught by that handler.
Multiple handlers (i.e., catch expressions) can be chained; each one with a different
parameter type. Only the handler whose argument type matches the type of the
exception specified in the throw statement is executed.
If an ellipsis (...) is used as the parameter of catch, that handler will catch any
exception no matter what the type of the exception thrown. This can be used as a
default handler that catches all exceptions not caught by other handlers:
try
{
// code here
}
catch (int param)
{
cout << "int exception";
}
catch (char param)
{
cout << "char exception";
}
catch (...)
{
cout << "default exception";
}
In this case, the last handler would catch any exception thrown of a type that is
neither int nor char.
After an exception has been handled the program, execution resumes after the try-
catch block, not after the throw statement!.
13
It is also possible to nest try-catch blocks within more external try blocks.
In these cases, we have the possibility that an internal catch block forwards the
exception to its external level. This is done with the expression throw; with no
arguments.
For example:
try
{
try
{ // code here
}
catch (int n)
{
throw;
}
}
catch (...)
{
cout << "Exception occurred";
}
14
• Logic_error
– domain_error
– invalid_argument
– length_error
– out_of_range
• Runtime_error
– Range_error
– overflow_error
Standard exception constructor To create an exception and throw it, call the con-
structor with a c-string parameter. throw out_of_range("Subscript less than zero");
Catching a standard exception You can catch an exception of a specific type, or any
of its subclasses, by specifying the class name as the type of the parameter in the
catch clause.
Call the what method to get the error string from a standard exception.
vector<int> v; // using standard vector class . . .
try { . . .
x = v.at(-2); // this throws out_of_range exception. . . .
} catch (out_of_range e)
{
cerr << e.what() << endl; // print error
exit(1); // stop program
}
15
The C++ Standard library provides a base class specifically designed to declare ob-
jects to be thrown as exceptions. It is called std::exception and is defined in the <ex-
ception> header. This class has a virtual member function called what that returns
a null-terminated character sequence (of type char *) and that can be overwritten in
derived classes to contain some sort of description of the exception.
// using standard exceptions
#include <iostream>
#include <exception>
using namespace std;
class myexception: public exception
{
virtual const char* what()
const throw()
{
return "My exception happened";
}
}
myex;
int main ()
{
try
{
throw myex;
}
catch (exception& e)
{
cout << e.what() << ’\n’;
}
return 0;
}
We have placed a handler that catches exception objects by reference (notice the
ampersand & after the type), therefore this catches also classes derived from excep-
tion, like our myex object of type myexception.
16
All exceptions thrown by components of the C++ Standard library throw exceptions
derived from this exception class. These are:
The following are exceptions with their descriptions.
Also deriving from exception, header <exception> defines two generic exception
types that can be inherited by custom exceptions to report errors: exception de-
scription logic_error error related to the internal logic of the program runtime_error
error detected during runtime
A typical example where standard exceptions need to be checked for is on memory
allocation: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // bad_alloc standard exception
#include <iostream>
#include <exception>
using namespace std;
int main ()
{
try
{
int* myarray= new int[1000];
}
catch (exception& e)
{
cout << "Standard exception: " << e.what() << endl;
}
return 0;
}
17
The exception that may be caught by the exception handler in this example is a
bad_alloc. Because bad_alloc is derived from the standard base class exception, it
can be caught (capturing by reference, captures all related classes).
2.4. Summary
Exceptions allow passing information about some exception condition (usually an
error) by means of the throw statement to the catch clause that encloses it or any of
the calls on the call stack. The calls look identical, and the error processing code is
in the function that wants to catch it.
Exceptions provide a way to react to exceptional circumstances (like runtime errors)
in programs by transferring control to special functions called handlers.
Revision Questions
18
LESSON 3
Standard Template Library
Learning outcomes
Upon completing this topic, you should be able to:
19
• Sequence: are ordered collections in which every element has a certain po-
sition.This position depends on the time and place of the insertion,but it
is independent on the value of the element.These include classes like vec-
tor,deque,and a list.
20
• Vector: provides a dynamic array structure with fast random access to any
element. Inserting and deleting elements at the end is fast. Can do subscript
bounds checking.
• Deque: also provides a dynamic array structure with random access and adds
fast insertion and deletion of elements at front as well as from the back. Very
slightly slower than vector because of an extra level of indirection.
• Map: provides access to elements using any type of key. This is a general-
ization of the idea of accessing a vector with an integer subscript.
• Multimap: is the same as map, but allows a key to map into more than one
element.
• Priority queue: always returns the element with the highest priority.
21
• String: holds character strings - similar to vector<char>, but with many use-
ful utility functions.
• Bitset: is a storage-efficient data structure for bits. By defining the bit opera-
tions, it effectively implements set operations.
3.3. Iterators
Iterators are used to access members of the container classes, and can be used in a
similar manner to pointers. For example, one might use an iterator to step through
the elements of a vector. There are several different types of iterators:
• Random_iterator-Read and write values with random access. These are the
most powerful iterators, combining the functionality of bidirectional iterators
with the ability to do pointer arithmetic and pointer comparisons.
22
Each of the container classes is associated with a type of iterator, and each of the
STL algorithms uses a certain type of iterator. For example, vectors are associated
with random-access iterators, which means that they can use algorithms that require
random access. Since random-access iterators encompass all of the characteristics
of the other iterators, vectors can use algorithms designed for other iterators as well.
The following code creates and uses an iterator with a vector:
vector<int> the_vector;
vector<int>::iterator the_iterator;
for( int i=0; i < 10; i++ ) the_vector.push_back(i);
int total = 0;
the_iterator = the_vector.begin();
while( the_iterator != the_vector.end() )
{
total += *the_iterator; the_iterator++;
}
cout << "Total=" << total << endl;
Notice that you can access the elements of the container by dereferencing the iter-
ator. Vectors (Sequence container): Arrays are a programming tool that provide a
mechanism to store a group of values under a single name. The values can be any
available data type (e.g., int, double, string, etc.). In C++, talk about vectors, rather
than arrays.
Vectors are declared with the following syntax:
vector<type> variable_name (number_of_elements);
Type refers to the data type of the elements of the array; variable_name is the name
that we assign to the vector, and the optional number_of_elements may be provided
to indicate how many elements the vector will contain.
Examples of vector declarations are the following:
vector<int> values (5); // declares a vector of 5 integers
vector<double> marks (20); // declares a vector of 20 doubles.
When we use vectors in our programs, we must provide, at the top of the file,
the line: #include <vector> After a vector has been declared specifying a certain
number of elements, we can refer to individual elements in the vector using square
brackets to provide a subscript or an index. In the following example, the program
asks the user for the marks for a group of 20 students and stores them in a vector:
23
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<double> student_marks (20);
for (int i = 0; i < 20; i++)
{
cout << "Enter marks for student #" << i+1 << ": ";
cin >> student_marks[i];
}
return 0;
}
The first statement (right after main) declares a vector called student_marks with
capacity to hold 20 values of type double. These values can be accessed individually
as student_marks[0] to student_marks[19]. The for loop has the counter i go from
0 to 19, allowing access to each individual element in a sequential manner, starting
at 0 and going through each value from 0 to 19, inclusively.
Vectors have one important advantage with respect to arrays in C, C++ and in other
languages: vectors can be resized during the execution of the program to accom-
modate extra elements as needed. Many other programming languages suffer the
limitation of fixed-size arrays; that is, once the arrays are declared with a size, they
can not be resized later in the program. In the example above, if we do know in ad-
vance that there are 20 students. Remember we could also ask the user how many
students are there, and resize the vector accordingly.
This is as shown in the example below:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<double> student_marks; // no size specified: vector contains
// zero elements
int num_students;
24
25
26
This statement declares an iterator to iterate over the elements of a vector of ints.
The iterator variable (in the above example, i) allows us to access a particular ele-
ment, and to move to the next element (those two operations are clearly necessary
if we want to iterator over all the elements of the vector).
To access the element "pointed to" by the iterator, we place a * operator in front.
To advance to the next element, we use the ++ operator (either pre-increment or
post-increment, although with iterators, it is slightly more efficient to use the pre-
increment form).
So, the following fragment of code prints an element and moves the itertator to the
next element:
cout << *i << endl; ++i;
We need to do this in a loop, which means that we need to make our iterator start at
the beginning of our vector, and loop while it still is within the vector’s elements.
To do this, we use the .begin() and .end() methods of vector.
These methods give us iterators positioned at the beginning and at the end of the
vector (more specifically, .end() will give us an iterator positioned at one-past-the-
last element, such that we can use it to control the loop).
The following fragment of code illustrates the use of iterators to print all the ele-
ments of a vector: // values has been declared as:
vector<int> values;
for (vector<int>::iterator i = values.begin(); i != values.end(); ++i)
{
cout << *i << endl;
}
Notice that, because values.end()is an iterator placed at one-past-the-last element,
when i is equal to that value, then we exit the loop, since that means that we are now
outside the range of the vector’s elements. If we have a vector of strings and want
to print the elements (the strings) and their lengths, we could do the following: //
names has been declared as: vector<string> names;
for (vector<string>::iterator i = names.begin(); i != names.end(); ++i)
{
cout << "Name: " << *i << "\tLength: " << (*i).length() << endl;
}
Instead of writing (*i).length() (we need the parenthesis, because the dot operator
27
has higher precedence than the unary * operator), we could simply write i->length(),
as shown in the fragment below, which is exactly equivalent to the above: for (vec-
tor<string>::iterator i = names.begin(); i != names.end(); ++i) { cout << "Name: "
<< *i << "\tLength: " << i->length() << endl; }
2. The second for a list of class instances. They are used to show a simple
example and a more complex real world application.
Lets start with a simple example of a program using STL for a linked list: // Stan-
dard Template Library example
#include <iostream>
#include <list>
using namespace std;
// Simple example uses type int
main()
{
list<int> L;
L.push_back(0); // Insert a new element at the end
L.push_front(0); // Insert a new element at the beginning
L.insert(++L.begin(),2); // Insert "2" before position of first argument // (Place be-
fore second argument)
L.push_back(5);
L.push_back(6);
list<int>::iterator i;
for(i=L.begin(); i != L.end(); ++i)
cout << *i << " ";
cout << endl;
return 0;
}
28
Output:
02056
3.5. Summary
Stl is based on the cooperation of different well-structured components,keys of
which are containers,iterators and algorithms.
29
Revision Questions
E XERCISE 9. Write a program that uses a vector to store the names of 20 stu-
dents .
E XERCISE 10. Give atleast two types of STL components.
E XERCISE 11. Write a program that can be used to store 30 numbers in a linked
list..
E XERCISE 12. Differentiate between a vector and a list in C++ langauge
Problem. Normally in a program that uses STL components,it uses the namespace
and the template class of a particular class of the STL component.write a program
code to use a namespace include a STL component class template of your choice.
Write a program that reads words from the standard input and when the input is
reached prints out each word once, with a count of the number of times it occurred
in the input. You will need a map from strings to integers to keep track of the
number of occurrences of each word. Because there is as yet no easy way to get all
the words in a map, you will also need to maintain a vector or list of all the words
seen (but only one of each).
30
LESSON 4
Introduction to C++ Templates
Learning outcomes
Upon completing this topic, you should be able to:
4.1. Introduction
Introduction C++ templates are a powerful mechanism for code reuse, as they en-
able the programmer to write code that behaves the same for data of any type.
Suppose you write a function printData:
void printData(int value)
{
std::cout<<"The value is "<<value<<std::endl;
}
If you later decide you also want to print double values,
or std::string values, then you have to overload the function:
void printData(double value)
{
std::cout<<"The value is "<<value<<std::endl;
}
void printData(std::string value)
{
std::cout<<"The value is "<<value<<std::endl;
}
The actual code written for the function is identical in each case; it is just the type
of the variable value that changes, yet we have to duplicate the function for each
distinct type. This is where templates come in – they enable the user to write the
function once for any type of the variable value.
31
template<typename T>
void printData(T value)
{
std::cout<<"The value is "<<value<<std::endl;
}
That’s all there is to it1 - we can now use printData for any data that can be written
to a std::ostream.
Here, the template<typename T> part tells the compiler that what follows is a tem-
plate, and that T is a template parameter that identifies a type.
Then, anywhere in the function where T appears, it is replaced with whatever type
the function is instantiated for - e.g.:
If we were really writing a generic function like this, we would probably pass the
parameter as a const reference,
rather than as a value parameter to avoid copying large objects - the same applies to
void printData(std::string value) above,
it would be more usual to write void printData(const std::string& value)
int i=3;
double d=4.75;
std::string s("hello");
bool b=false;
printData(i); // T is int
printData(d);// T is double
printData(s); // T is std::string
printData(b);// T is bool
It is possible to write class templates as well as function templates like printData.
A common example is std::vector - you can specify a vector of integers, or of
strings, or of some user-defined class, simply by specifying the template param-
eter:
class MyClass{};
std::vector<int> vi; // contains ints
std::vector<double> vd; // contains doubles
std::vector<std::string> vs; // contains std::strings
std::vector<MyClass> vmc; // contains MyClass objects
32
• Template parameters.
Two template instantiations refer to the same template if their parameters are all the
same, irrespective of any typedefs that may apply.
Therefore vec1, vec2 and vec3 in the following example are all the same type.
typedef std::string MyString;
typedef std::vector<std::string> T1;
typedef std::vector<MyString> T2;
T1 vec1;
T2 vec2;
std::vector<std::string> vec3;
Multiple parameters may be specified, separated by commas in the template param-
eter list:
template<typename T1,typename T2>
class MyClass{ };
MyClass is thus a class template with two template type parameters, T1 and T2
Every time a template is referenced with distinct template arguments,
then the template is instantiated with the arguments substituted as explained in the
following sections.
33
If the resultant code is not valid, then a compilation error will occur.
34
35
template<int i>
void func()
{
std::cout<<i<<std::endl;
}
func<3>() will print 3, and func<999>() will print 999.
A compile-time constant is something that can be evaluated at compile-time, such
as an integer, or the address of a global variable.
There are restrictions on what can be a compile-time constant, e.g. floating point
values can not be compile-time constants.
36
37
names that are members of types that are themselves dependent names are always
assumed to name objects or functions rather than types,unless preceded with the
typename keyword. e.g.
struct X
{
int x;
typedef double Z;
};
struct Y
{
typedef int x;
double Z;
};
template<typename T>
struct ZZ
{
T::Z z1;// 1
typename T::Z z2; // 2
void func(T& t)
{
t.x=4; // 3
}
typedef typename std::vector<T>::iterator VecIt; // 4
};
int main()
{
X x;
Y y;
ZZ<X> zzx; // 5 ZZ
<Y> zzy; // 6
zzx.func(x); // 7
zzy.func(y); // 8
}
38
39
40
// T=int
func(3);
// T=double
func(3.5);
// T=int, U=double
func2<int>(3.5);
// T=std::vector<std::string>, U=int
func2<std::vector<std::string> >(5);
// specify both T and U
// T=std::vector<std::string>, U=int
func2<std::vector<std::string>,int>(5.7);
}
This Template Argument Deduction can be used to make life easier for the user of
a function template, the same way that function overloading makes life
easier for the user of a normal function - the correct function instantiation is auto-
matically called based on the function arguments provided.
In some circumstances, Template Argument Deduction will fail, because there is an
inconsistency caused during deduction - if two parameters are declared
to be the same type, and different types are passed, then the compiler cannot deduce
which to use. This often occurs with std::max: int i=std::max(3,4.5);
The compiler cannot deduce whether to instantiate std::max<int>(int,int) or std::max<double>(double,do
so it generates an error. The solution is to
provide an explicit template argument list, or cast one of the arguments so it is the
same type as the other.
int i=std::max(static_cast<double>(3),4.5);
// T=double int j=std::max<int>(3,4.5);
// T=int
Note also that the return type is not considered when deducing the template argu-
ments in this way. The Standard C++ Library contains a large number
of templates, which is partly what makes it so powerful - the Standard Library code
can be successfully used with classes that didn’t exist when the
Standard was written.
41
• As a consequence, the std::auto ptr template does not fulfil these require-
ments, as it exhibits transferof- ownership semantics on copy-construction
and assignment, and consequently the copy-constructor and copyassignment
arguments are of type reference-to-T rather than const-reference-to-T. It is
possible to write Concept-checkers, templates which verify that a given type
does indeed fulfil all the syntactic requirements of a particular Concept, even
42
if the current template doesn’t require all of them in its current implemen-
tation. This also makes tracking down errors easier - the compilation error
generated by a failure in such a Concept-Checker is more obviously a failure
of the parameter type to fulfil the concept requirements than a failure else-
where in the template definition. It is possible that a class template may sup-
port different operations, depending on which of several concepts a parameter
type fulfils the requirements for. This is made possible by a feature of C++
class templates - member functions of class templates are only instantiated
if they are referenced. This means that a class template can have a member
function which only compiles if the template parameters fulfil a particular
concept, but the program will compile even if they don’t fulfil the concept,
provided that the function is not referenced for that set of parameters. e.g.:
4If a class does not define any copy-constructors (constructors which can be
called with a single argument of type reference-to-T or const-reference-to-
T), then the compiler will generate one automatically. 5If a class does not
define any copy-assignment operators which can be called with a single argu-
ment of type T, reference-to-T or const-reference-to-T, then the compiler will
generate one automatically.
template<typename T>
class MyClass
{
public:
T* makeCopy(T* p)
{
return p>
clone();
}
};
MyClass<int> mci;
double d;
MyClass<double> mcd;
double* pd=mcd.makeCopy(&d);
mci is fine; as no reference is made to the makeCopy member, it doesn’t matter that
int isn’t a class. However, the reference to mcd.makeCopy causes an error,
43
44
This means that we cannot use our original function template func for T3.
45
46
47
48
swap to our class which swaps the value with that of another object as efficiently as
possible
class ExpensiveToCopy
{
public:
void swap(ExpensiveToCopy& other);
};
To call this, we now say a.swap(b) rather than swap(a,b), which is inconsistent. We
can solve this by specializing the function template swap
for ExpensiveToCopy:
template<>
void swap<ExpensiveToCopy>(ExpensiveToCopy& lhs,ExpensiveToCopy& rhs)
{
lhs.swap(rhs);
}
All is well and good, but what if ExpensiveToCopy was a class template, such as
std::vector; we don’t want to have to specialize swap for every
possible instantiation of the template. The solution to this is Function Template
Overloading - we just write a new template function which overloads the
first, e.g.:
template<typename T>
void swap(std::vector<T>& lhs,std::vector<T>& rhs)
{
lhs.swap(rhs);
}
The compiler chooses which overloaded template to instantiate for each call using
a mechanism called Partial Ordering in addition to the normal
overload resolution. This is quite complicated, but essentially results in more spe-
cific overloads, like that of swap for std::vector being preferred to the
more general equivalent, if the actual parameters are compatible with the more
specific one. If Function Template Overloading does not suit your needs, and
you really do need Partial Specialization (4.2), the only option is to write a helper
class template, with a single static member function that does all the work,
49
and have your function template forward to this function. You can then partially
specialize the helper class template to change the body of the static member. e.g.:
template<typename T>
class MyClass
{
public:
void write(std::ostream& os) const;
};
14 template<typename T>
struct PrintDataHelper
{
static void doPrint(T value)
{
std::cout<<"The data is "<<value<<std::endl;
}
};
template<typename T> struct PrintDataHelper<MyClass<T> >
{
static void doPrint(const MyClass<T>& value)
{
std::cout<<"The data is "; value.write(std::cout);
std::cout<<std::endl;
}
};
template<typename T> void printData(T value)
{
PrintDataHelper<T>::doPrint(value);
}
4.5. Summary.
This module provides an insight into how they work and how they can be utilised
to make the task of writing and maintaining software easier. They provide immense
scope for writing generic and customizable classes and functions, and form an es-
50
sential C++ language feature, without which much of the Standard C++ Library
couldn’t exist, or would be greatly restricted.
C++ templates are a powerful mechanism for code reuse, as they enable the pro-
grammer to write code that behaves the same for data of any type.
Partial Specialization allows you to specialize a class template for a subset of the
possible template parameters, where the definition of the template should be the
same for the whole subset, but different to the general case.
51
Revision Questions
52
LESSON 5
Introduction notes on files programming
Learning outcomes
Upon completing this topic, you should be able to:
• Understand Files
• Write a program that can write and read a text from a file.
One important thing to note is that the input is treated the same as cin while the
output is treated the same as cout.
53
5.1.1. Output
In order to do both input or output, you must open a file before anything else occurs.
The same as a variable, the stream you are using is a type. So here is a short program
that shows some simple output to a text file.
Example 1:
Simple output
#include <iostream>
#include <fstream>
using namespace std;
int main ()
{
//declare the variable of type ofstream
//since you are dealing with output:
ofstream myfile;
//function to open the file which includes
//the file name:
myfile.open ("example.txt");
//check if the file is open with the is_open()
//function: if(myfile.is_open())
{
//preform the operation(s):
myfile << "Hello world! This is output!" << endl;
//function to close the file: myfile.close();
}
else{
//is_open() returned false and there is a problem: cout << "Can’t open the file!" <<
endl;
}
return 0;
}
The above program will simply open a text file for output (ofstream), check to see
if the file is correctly opened, write a line to the file and close the stream.
Here is a bit more detail about the above programs functions.
54
• open()
The open() function will simply open a file for either input or output. The above
program did it for output. The parameter of the function will be the file name.
If the file does not exist in the directory, C++ will create it for you.
• close()
A simple function designed to close the file and it’s stream. It will require no
parameters.
• is_open()
The is_open() function is a boolean function that will check whether or not a file is
open. If the function returns true, the file is open without any problems. If it returns
false, the file is not good and therefore cannot be used.
5.1.2. Input
Recalling from the previous tutorial about the getline() function, input from text
filesare used in that fashion. Input of files also require the <string>library for the
getline() function. Here is an example of file input. The file must be created first in
order for this to work.
Example 2:
Simple input
// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main ()
55
{
string line;
//the variable of type ifstream: ifstream myfile ("example.txt");
//check to see if the file is opened:
if (myfile.is_open())
{
//while there are still lines in the //file, keep reading:
while (! myfile.eof() )
{
//place the line from myfile into the //line variable: getline (myfile,line);
//display the line we gathered: cout << line << endl;
}
//close the stream: myfile.close();
}
else cout << "Unable to open file";
return 0;
}
This program will attempt to read a file for input, display the line you read and then
close the file. Here is some information about the new function eof()
which stands for "end of file".
eof()
The eof() function is a boolean function that will check whether or not the file has
reached the end. It returns true when the file is at the end and false otherwise.
5.2. Writing data to file
The commands for writing data to a file are (for the most part) very similar to those
used to write data to the screen.. In order for your program to write to a
file, you must:
56
6. close the file when access is no longer needed (optional, but a good practice)
using std::cerr;
using std::endl;
#include <fstream>
using std::ofstream;
#include <cstdlib>
// for exit function
// This program output values from an array to a file named example2.
dat int main()
{
ofstream outdata;
// outdata is like cin int i;
// loop index int num[5] = {4, 3, 6, 7, 12};
// list of output values outdata.open("example2.dat");
// opens the file if( !outdata )
{
// file couldn’t be opened cerr << "Error: file could not be opened" << endl;
exit(1);
}
for (i=0; i<5; ++i)
outdata << num[i] << endl;
outdata.close();
return 0;
}
In this example, the variable outdata is declared as type ofstream. It is used in the
same way that cout is used for doing output to the screen. Several values may be
written to the same output record as follows:
57
outdata << num1 << " " << num2 << " " << num3 << endl;
Spaces are inserted in this statement to separate the output values. When opening
a file, you should always check for success before continuing. The ! operator for
the filename returns true if the file can’t be opened. You might want to use the
following stream manipulators: hex, dec, setw, setfill.
You might also be using the following arguments to setf, unsetf, setiosflags and
resetiosflags: ios::uppercase, ios::left, ios::right. Use of stream manipulators re-
quires the iomanip header file and appropriate using declarations.
6. after each read, check for end-of-file using the eof() member function
7. close the file when access is no longer needed (optional, but a good practice)
using std::cerr;
using std::cout;
using std::endl;
#include <fstream>
using std::ifstream;
#include <cstdlib> // for exit function
58
59
can’t be opened. The value of indata.eof() is true if the most recent attempt to read
a value was unsuccessful (i.e. no more data).
The following example shows how to read one character at a time.
char letter;
// variable for input value
indata >> letter;
while ( letter != ’Q’ )
{
cout << letter;
indata >> letter;
}
cout << endl;
INPUT: ab 12 3Q
OUTPUT: ab123
The above example reads and echoes characters from the file until the letter Q is
encountered, at which point the program stops.
Note that the spaces (whitespace) are skipped over and do not appear in the output-
only 6 characters are actually read.
The next example shows how to read an entire string using the >> operator.
char word[10]; // variable for input value
indata >> word;
cout << word << endl;
INPUT1: ab123 5670 sdQzx | INPUT2: abcdefghijklmnop qrstuvwxyz
OUTPUT1: ab123 | OUTPUT2: ?????????????
When reading character values into a string variable using >> , characters are read
until whitespace is encountered. It is the programmer’s responsibility to make sure
that the string variable is large enough to store all the characters that are read.
Thus, for INPUT1, the characters ’ab123’ are read and stored in the variable word.
The space between the ’3’ and the ’5’ terminates the read operation.
In the second example, the programmer made a mistake since the string has only
space for 10 characters, but the computer would continue reading up to the letter
’p’. The actual output in this situation would depend on the computer used. In some
situations, it is desirable to read an entire line of input, including the whitespace.
60
For example: to read a name such as ’ JKUAT community’ into a single string
variable. This can be done using the getline member function.
char word[10]; // variable for input value
indata.getline(word, 10, ’\n’);
cout << word << end;
INPUT1: abcdefg // entire line is read into word
OUTPUT1: abcdefg INPUT2: abcdefghiABCDEFGHIJ // first 9 chars are read in
OUTPUT2: adcdefghi
In this example, the getline function will read characters from the file, placing them
in the array word, until 9 characters have been read (leaving one space for the NULL
character) or until the delimiter ’\n’ is read. If there are more than 9 characters on
the line, then those ’extra’ characters remain in the input buffer.
If there are 8 or fewer characters, the whole line is read in and the delimiter is re-
moved from the buffer. Note that if there are exactly 9 characters, then the delimiter
is not removed from the buffer. The peek() function is used to read one character
from the input stream while leaving the character in the buffer.
The character will be read with the next input statement. The putback() function is
used to return the character most recently read to the input stream.
3. Declare an input file stream (ifstream) variable. For example, ifstream inFile;
4. Open the file stream. -Path names in MS Windows use backslashes (\). Be-
cause the backslash is also the string escape character, it must be doubled. If
the full path is not given, most systems will look in the directory that contains
the object program. For example, inFile.open("C:\\temp\\datafile.txt");
5. Check that the file was opened. For example, the open fails if the file doesn’t
exist, or if it can’t be read because another program is writing it.
61
A failure can be detected with code like that below using the ! (logical not)
operator:
if (!inFile)
{ cerr << "Unable to open file datafile.txt";
exit(1); // call system to stop
}
Closing is essential for output streams to be sure all information has been written to
the disk, but is also good practice for input streams to release system resources and
make the file available for other programs that might need to write it.
inFile.close();
Example
The following program reads integers from a file and prints their sum.
// io/read-file-sum.cpp - Read integers from file and print sum.
// The author of the module.E.O.O
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace std;
int main()
62
{
int sum = 0;
int x;
ifstream inFile;
inFile.open("test.txt");
if (!inFile)
{
cout << "Unable to open file";
exit(1);
// terminate with error
}
while (inFile >> x)
{
sum = sum + x;
}
inFile.close();
cout << "Sum = " << sum << endl;
return 0;
}
5.4.1. Reading numbers
Read from in a while loop
The standard C/C++ style for reading is to put the read operation in a while loop
condition. If the read proceeds correctly, then the value is true. If there is an end-
of-file (EOF) meaning that the end of the data has been reached, or if there is an
error in reading, then the value returned is false and execution continues at the end
of the loop.
Example :Adding numbers in the input
int sum = 0;
int x;
while (cin >> x)
{
sum += x;
}
cout << sum;
63
The following code does almost the same thing as the code above, but it has disad-
vantages.
int sum = 0;
int x;
cin >> x;
// BAD idiom for input.
while (cin)
{
// Required by inadequate Pascal I/O.
sum += x;
// Should not be used in C++.
cin >> x;
}
cout << sum;
5.4.2. Reading Lines
These notes are written using cin as an example, but they apply to all input streams.
Strings (sequences of characters) are stored two different ways in C++. C-strings
are arrays of characters with a terminating zero value at the end.
There is also a string class in the C++ standared library. In general, it’s better to use
the string class, but many times that C-strings must be used.
Reading a line into a string
string s;
...
while (getline(cin, s))
{
// s contains the input line, without final newline char
....
}
Reading a line into a C-string
char ca[100];
...
while (cin.get(ca, 99))
{
// ca has one input line without crlf and with terminating 0
64
...
}
5.4.3. Reading Characters
These notes are written using cin as an example, but they apply to all input streams.
How to work with whitespace
The standard input stream (cin) does just what you want when reading integers,
floating point etc. It ignores all whitespace (blanks, tabs, etc) that appears in front
of a number. But when you’re reading characters, you will often want to read
whitespace too. For example, here’s a little loop to count the number of characters
in the input stream.
char c;
int count = 0;
while (cin >> c)
{
// DOESN’T READ WHITESPACE!!!!!
count++;
}
This fails to give an accurate count of characters because it ignores all the whites-
pace characters. There are two solutions.
• Use the noskipws I/O manipulator to cause a further cin reads to not ignore whites-
pace (or until the skipws manipulator is used).
Here’s that same loop rewritten.
• char c;
• cin >> noskipws;
// Stops all further whitespace skipping
• while (cin >> c)
{
// Reads whitespace chars now.
• count++;
}
This isn’t a bad solution, but if you need to switch back and forth between skipping
whitespace and not, the best approach is the next alternative.
• Use the cin.get() function.
• char c;
65
• while (cin.get(c))
{
// Always reads whitespace chars.
• count++;
}
Read one line with cin.get(...) or cin.getline(...)
char ca[100];
...
while (cin.get(ca))
{
// ca has one input line without crlf and with terminating 0
...
}
Ignoring input
When an error is encountered, it is often useful to skip all remaining characters on
the line. You can do that with:
cin.ignore(n, c); where n is the number of characters to skip, and c is the character
to skip up to, whichever comes first.
For example, to skip to the end of the line, cin.ignore(1000, ’\n’);
5.5. Summary on I/O streams
File handling is an important part of all programs. Most of the applications will
have their own features to save some data to the local disk and read data from the
disk again. C++ File I/O classes simplify such file read/write operations for the
programmer by providing easier to use classes.
C++ File I/O Classes and Functions:
There are 3 File I/O classes in C++ which are used for File Read/Write operations.
They are
• fstream - Can be used for both read/write c++ file I/O operations
66
The most important methods which will be used for any file operations are:
3. Close the file This sample code snippet explains how to use the c++ file i/o
stream operators to read data from a file.
67
One is the file path and the second is the File Open mode. There are several open
modes, each of them with a different purpose. Some of them are ios::
in for reading, ios::out for writing, ios::app for appending to the end of file, ios::binary
for opening in binary mode etc.,
Now for the purpose of this article, as the data is read from the file, the flag ios::in
is used. After this, the read operation is continued till the end of the file.
The while loop ensures a continuous read till the end of the file or it encounters any
abnormal break. If the program is built and run , it displays all the data
read from the file. The C++ File I/O read job is done.
But if we look at the output closely, there is a draw back in using this stream oper-
ator read.
The output misses the white spaces and the end of line characters. In order not to
miss these characters we can either use fstream::get() or fstream::getline
() methods.
Here is the example for using fstream getline method.
#include <fstream.h>
int main()
{ char str[2000];
fstream file_op("c:\\test_file.txt",ios::in);
while(!file_op.eof())
{ file_op.getline(str,2000);
cout <<str;
}
file_op.close();
cout <<endl;
return 0;
}
68
2. Write to a file
69
70
internal Distribute fill character between sign and value. left Align left.
oct -Use base 8.
right -Align right.
scientific Outputs float values in scientific format (use the resetiosflags(ios::floatfield)
manipulator or the unsetf(ios::floatfield) function to reset to
default format).
showbase -Encodes base on integer output.
showpoint -Include decimal point in output.
showpos-Include positive (+) sign in output.
skipws -Skip white space (spaces and tabs).
uppercase- Forces upper case output.
IOS File Access Flags (ios::x)
app- Open in append mode.
ate -Open and seek to end of file.
in- Open in input mode.
nocreate -Fail if file doesn’t already exist.
noreplace -Fail if file already exists.
out -Open in output mode.
trunc -Open and truncate to zero length.
binary- Open as a binary stream.
Example 1
#include<iostream.h>
#include<fstream.h>
#include<stdlib.h>
int main()
{
fstream optfil;
optfil.open("text.txt",ios::out);
char name[10];
if(optfil.fail())
{
cerr<<"The file not opened";
exit(1);
}
71
72
73
Revision Questions
Example . Name main three libraries that are used in C++ for file programming
Solution: #include <ifstream>:Library to specifically deal with input of text files.
Does not deal with output.
#include <ofstream>:Library to specifically deal with output of text files. Does not
deal with input.
#include <fstream>:Library to deal with BOTH input and output.
E XERCISE 18. Name three main issues to consider before perforing a write
operation on a file
E XERCISE 19. Differentiate between peek() and seekg() function in file pro-
gramming.
Problem. Write a program code that be used open a file “son.doc” using a construc-
tor function,and use a member function open of the class.You should also write the
word “sons of the people” on to the file.
74
LESSON 6
Early binding and late binding
Learning outcomes
Upon completing this topic, you should be able to:
• Write a program code that explains the static and dynamic binding.
75
{
std::cout << Val;
}
int main()
{
displayVal(7);
// This is a direct function call
return 0;
}
Direct function calls can be resolved using a process known as early binding. Early
binding (also called static binding) means the compiler is able to directly associate
the identifier name (such as a function or variable name) with a machine address.
Remember that all functions have a unique machine address. So when the compiler
encounters a function call, it replaces the function call with a machine language
instruction that tells the CPU to jump to the address of the function.
This is a simple calculator program that uses early binding:
#include <iostream>
using namespace std;
int Add(int i, int j)
{
return i + j;
}
int Subtract(int i, int j)
{
return i- j;
}
int Multiply(int i, int j)
{
return i * j;
}
int main()
{
int i;
cout << "Enter a number: ";
76
cin >> i;
int j;
cout << "Enter another number: ";
cin >> j;
int op;
do
{
cout << "Enter an operation (0=add, 1=subtract, 2=multiply): ";
cin >> op;
}
while (op< 0 || op > 2);
int Results = 0;
switch (op)
{
case 0: Results = Add(i,j);
break;
case 1: Results = Subtract(i,j);
break;
case 2: Results = Multiply(i,j);
break;
}
cout << "The answer is: " << Results << endl;
return 0;
}
Because Add(), Subtract(), and Multiply() are all direct function calls, the compiler
will use early binding to resolve the Add(), Subtract(), and Multiply() function
calls. The compiler will replace the Add() function call with an instruction that
tells the CPU to jump to the address of the Add() function. The same holds true for
Subtract() and Multiply().
77
binding). In C++, one way to get late binding is to use function pointers. To review
function pointers briefly, a function pointer is a type of pointer that points to a
function instead of a variable. The function that a function pointer points to can
be called by using the function call operator (()) on the pointer. For example, the
following code calls the Add() function:
int Add(int i, int j)
{
return i+ j;
}
int main()
{
// Create a function pointer and make it point to the Add function
int (*func)(int, int) = Add;
cout << func(23, 12) << endl; // add 23 + 12
return 0;
}
Calling a function via a function pointer is also known as an indirect function call.
The following calculator program is functionally identical to the calculator example
above, except it uses a function pointer instead of a direct function call:
#include <iostream>
using namespace std;
int Add(int i, int j)
{
return i + j;
}
int Subtract(int i, int j)
{
return i-j;
}
int Multiply(int i, int j)
{
return i *j;
}
int main()
78
{
int i;
cout << "Enter a number: ";
cin >> i;
int j;
cout << "Enter another number: ";
cin >> j;
int op;
do
{
cout << "Enter an operation (0=add, 1=subtract, 2=multiply): ";
cin >> op;
}
while (op < 0 ||op > 2);
// Create a function pointer named Func
int (*Func)(int, int);
// Set Func to point to the function the user chose
switch (op)
{
case 0:
Func = Add;
break;
case 1:
Func = Subtract;
break;
case 2:
Func = Multiply;
break;
}
// Call the function that Func is pointing to with i and j as parameters
cout << "The answer is: " << Func(i,j) << endl;
return 0;
}
79
6.4. Summary
Late binding is slightly less efficient since it involves an extra level of indirection.
With early binding, the compiler can tell the CPU to jump directly to the function’s
address. With late binding, the program has to read the address held in the pointer
and then jump to that address. This involves one extra step, making it slightly
slower. However, the advantage of late binding is that it is more flexible than early
binding, because decisions about what function to call do not need to be made until
run time.
80
Revision Questions
81
LESSON 7
C++ Programming Operator Overloading
Learning outcomes
Upon completing this topic, you should be able to:
• Understand operators
82
83
84
2. There are two operators assignment operator(=) and address operator(&) which
does not need to be overloaded. Because these two operators are already over-
loaded in C++ library. For example: If obj1 and obj2 are two objects of same
class then, you can use codeobj1=obj2; without overloading = operator. This
code will copy the contents object ofobj2 to obj1. Similarly, you can use ad-
dress operator directly without overloading which will return the address of
object in memory.
4. Not all operators in C++ language can be overloaded. The operators that
cannot be overloaded in C++ are ::(scope resolution), .(member selection),
.*(member selection through pointer to function) and ?:(ternary operator).
85
Again, the above example is increase count by 1 is not complete. This program is
incomplete in sense that, you cannot use code like:
t1=++t
It is because the return type of operator function is void. It is generally better to
make operator work in similar way it works with basic types if possible. Here,
are the examples of operator overloading on different types of operators in C++
language in best possible ways: Increment ++ and Decrement – Operator Over-
loading.
86
Check obj;
/* Displays the value of data member i for object obj */
obj.Display();
/* Invokes operator function void operator ++( ) */
++obj;
/* Displays the value of data member i for object obj */
obj.Display();
return 0;
}
Output
i=0
i=1
Explanation
Initially when the object obj is declared, the value of data member i for object obj is
0( constructor initializes i to 0). When ++ operator is operated on obj, operator func-
tion void operator++( ) is invoked which increases the value of data member i to 1.
This program is not complete in the sense that, you cannot used code: obj1=++obj;
It is because the return type of operator function in above program is void. Here is
the little modification of above program so that you can use code obj1=++obj.
/* C++ program to demonstrate the working of ++ operator overlading. */
#include <iostream>
using namespace std;
class Check
{
private: int i;
public: Check(): i(0){ }
Check operator ++() /* Notice, return type Check*/
{
Check temp; /* Temporary object check created */
++i; /* i increased by 1. */
temp.i=i; /* i of object temp is given same value as i */
return temp; /* Returning object temp */
}
void Display()
87
{
cout<<"i="<<i<<endl;
}
};
int main()
{
Check obj, obj1;
obj.Display();
obj1.Display();
obj1=++obj;
obj.Display();
obj1.Display();
return 0;
}
Output
i=0
i=0
i=1
i=1
This program is similar to above program. The only difference is that, the return
type of operator function is Check in this case which allows to use both codes ++obj;
obj1=++obj;. It is because, temp returned from operator function is stored in object
obj. Since, the return type of operator function is Check, you can also assign the
value of obj to another object. Notice that, = (assignment operator) does not need
to be overloaded because this operator is already overloaded in C++ library.
88
class Check
{
private: int i;
public: Check(): i(0)
{}
Check operator ++ ()
{
Check temp;
temp.i=++i;
return temp;
}
/* Notice int inside barcket which indicates postfix increment. */
Check operator ++ (int)
{
Check temp;
temp.i=i++;
return temp;
}
void Display()
{
cout<<"i="<<i<<endl;
}
};
int main()
{
Check obj, obj1;
obj.Display();
obj1.Display();
obj1=++obj; /* Operator function is called then only value of obj is assigned to
obj1. */
obj.Display();
obj1.Display();
obj1=obj++; /* Assigns value of obj to obj1++ then only operator function is called.
*/
89
obj.Display();
obj1.Display();
return 0;
}
Output
i=0
i=0
i=1
i=1
i=2
i=1
When increment operator is overloaded in prefix form; Check operator ++ () is
called but, when increment operator is overloaded in postfix form; Check operator
++ (int) is invoked. Notice, the int inside bracket. This int gives information to
the compiler that it is the postfix version of operator. Don’t confuse this int doesn’t
indicate integer.
Operator Overloading of Decrement – Operator
Decrement operator can be overloaded in similar way as increment operator. Also,
unary operators like: !, ~ etc can be overloaded in similar manner.
90
private:
float real;
float imag;
public:
Complex(): real(0), imag(0){ }
void input()
{
cout<<"Enter real and imaginary parts respectively: ";
cin>>real;
cin>>imag;
}
Complex operator - (Complex c2) /* Operator Function */
{
Complex temp;
temp.real=real-c2.real;
temp.imag=imag-c2.imag;
return temp;
}
void output()
{
if(imag<0)
cout<<"Output Complex number: "<<real<<imag<<"i";
else cout<<"Output Complex number: "<<real<<"+"<<imag<<"i";
}
};
int main()
{
Complex c1, c2, result;
cout<<"Enter first complex number:\n";
c1.input();
cout<<"Enter second complex number:\n";
c2.input(); /* In case of operator overloading of binary operators in C++ program-
ming, the object on right hand side of operator is always assumed as argument by
compiler. */
91
7.7. Summary
Though, this module contains the overloading of - operators, binary operators in
C++ programming like: +, *, <, += etc. can be overloaded in similar manner.
Increment ++ and decrements – operator are overloaded in best possible way, i.e.,
increase the value of a data member by 1 if ++ operator operates on an object and
decrease value of data member by 1 if – operator is used.
92
Revision Questions
Problem. Discuss how binary operators in C++ programming like: +, *, <, += can
be overloaded.
93
LESSON 8
Component-Based Developments
Learning outcomes
Upon completing this topic, you should be able to:
94
8.1. Introduction
This is to provide direction and guidance to C++ programmers that will enable them
to employ good programming style and proven programming practices leading to
safe, reliable, testable, and maintainable code.
C++ was designed to support data abstraction, object-oriented programming, and
generic programming while retaining compatibility with traditional C programming
techniques. Component based software engineering is the software development
model that uses different software modules and puts them together for a larger sys-
tem. Component based software engineering is based on the notion that there is a
library of first and third party certified components that developers can use to create
the desired functionality. These components are to be certified based on a standard
where developers can rely on similar assumptions about each component. This type
of model allows a reliable, flexible and fast form of software development. In to-
day’s market a quick and flexible model is beneficial to today’s changing business
needs.
One of the most promising development models is component based software en-
gineering. This model is based on the notion that developers can select appropriate
off-the-shelf software components and build them together using well defined soft-
ware architecture. Component based software engineering is concerned with the
rapid assembly of systems from components. These components and frameworks
have certified properties; which provide the basis for predicting the properties of
systems built from components. This kind of software approach is very different
from the traditional approach in which the software is built from the ground up.
Each of these commercial off-the-shelf (COTS) components can be developed by
different companies using even different languages and platforms.
If you look at Figure 1 you can see how these COTS can be taken out of a component
repository and assembled into a target software system
95
• Over budget,
• Many software projects produced software which did not satisfy the require-
ments of the customer.
• Demand of new software increased faster than ability to generate new soft-
ware.
96
All the above attributes of what was called a ‘Software Crisis’. So the term ‘Soft-
ware Engineering’ first introduced at a conference in late 1960’s to discuss the
software crisis.
• Portability: Source code should be portable (i.e. not compiler or linker de-
pendent).
97
Remark. Note that following the guidelines contained within this module will not
guarantee the production of an error-free, safe product. However, adherence to
these guidelines, as well as the processes defined in the Software Development Plan
, will help programmers produce clean designs that minimize common sources of
mistakes and errors.
• Many functions accessing global data. There may be times where tightly
coupled software is unavoidable, but its use should be both minimized and
localized.
98
99
terns component frameworks are used to describe a larger unit of design, which
is more specialized. Another way to think of a component framework is a mini
operating system. Components in this case would be what processes are to an op-
erating system. The framework manages shared resources and provides methods of
communication among components. However unlike operating systems component
frameworks do not deal with runtime existence.
1. Requirements analysis;
4. System Integration;
5. System testing;
6. Software Maintenance
100
101
form and programming languages. System architecture design should address the
advantage for selecting a particular architecture from other architectures. The sys-
tem integration process itself is the process of assembling the selected components
into the whole system based on the designed system architecture. The obvious final
objective of system integration is the final system that is composed of the selected
components. The integration process involves a few steps: initial integration, test-
ing, changing the component and reintegration (if necessary). When this phase is
completed the system should be in its final form and ready for complete system test-
ing. Also leaving this phase requires a document that will specify what is required
during the software maintenance phase.
102
ically for software components most of the maintenance on the system will be on
the software framework or architecture designed for the system. Maintenance on
the components themselves needs to be handled by the component developer. A
support strategy for the system will reflect this and focus primarily on the services
related to communication between software components. The output of this phase
would then be a newer version of the system that can be retested based on the past
testing strategy and additional tests that may also be needed.
8.7. Summary
Component based software engineering is a very interesting and useful software de-
velopment model. It provides an easy way to assemble software using components
that have been previously developed. It offers a quick and cheap way to develop
software systems. As its popularity grows the market for already development soft-
ware components grows and offers an interesting direction for software evolution.
In order for this type of model to see success there is a great need for well de-
103
fined standards for components. These standards would make it possible to assume
quality for software components and make it easy for large system developers to fit
components into their software frameworks.
Revision Questions
Problem. Discuss in a group and write a report on how to ensure Quality in Com-
ponent Based Software Engineering.
104
LESSON 9
Introduction to Sockets Programming
Learning outcomes
Upon completing this topic, you should be able to:
105
106
• UDP (datagram).
Use of sockets:
9.3. What are Threads, Ports, and how are related to Sockets?
A "thread" is a symbolic name for a connection between your computer and a re-
mote computer, and a thread is connected to a socket.
A socket can be opened on any "port"; which is simply a unique number to dis-
tinguish it from other threads, because more than just one connection can be made
on the same computer. A few of these ports have been set aside to serve a specific
purpose. Beyond these ports, there are quite a large number of other ports that can
be used for anything and everything: over 6,000, actually. A few commonly used
ports are listed below in figure 1 with their corresponding services:
107
108
109
110
else
return true; //Success
}
//CLOSECONNECTION – shuts down the socket and closes any connection on it
void CloseConnection ()
{
//Close the socket if it exists if (s) closesocket(s);
WSACleanup(); //Clean up Winsock
}
Receiving Connections – Acting as a Server
This code explain how to play the "server" role; so as remote computers can connect
to it. The server can "listen" on any port and await an incoming connection as usual.
int PASCAL bind(SOCKET,const struct sockaddr*,int); //bind to a socket
int PASCAL listen(SOCKET,int); //Listen for an incoming connection
//Accept a connection request
SOCKET PASCAL accept(SOCKET,struct sockaddr*,int*);
an receive requests for a connection on the port you are listening on: say, for ex-
ample, a remote computer wants to chat with your computer, it will first ask your
server whether or not it wants to establish a connection. In order for a connection to
be made, your server must accept() the connection request. Note that the "server"
decides whether or not to establish the connection. Finally, both computers are
connected and can exchange data.
Although the listen() function is the easiest way to listen on a port and act as the
server, it is not the most desirable. You will quickly find out when you attempt it
that your program will freeze until an incoming connection is made, because listen()
is a "blocking" function – it can only perform one task at a time, and will not return
until a connection is pending.
Before communication- listening on a port is accomplished, you must:
1. Initialize Winsock
2. Start up a socket and make sure it returns a nonzero value, which signifies
success and is the handle to the socket
3. Fill out the SOCKADDR_IN structure with the necessary data, including the
address family, port, and IP address.
111
4. Use bind() to bind the socket to a specific IP address (if you specified inet_addr("0.0.0.0")
or htonl(INADDR_ANY) as the sin_addr section of SOCKADDR_IN, you
can bind to any IP address)
The first parameter of listen() must be the handle to a socket,You can then specify,
with the next and final parameter, how many remote computers can communicate
with your server at the same time. Generally, however, unless you want to exclude
all but one or a few connections, we just pass SOMAXCONN (SOcket MAX CON-
Nection) as the final parameter to listen(). If the socket is up and working fine, all
should go well, and when a connection request received, listen() will return. This is
your clue to call accept(), if you wish to establish a connection.
#include <windows.h>
#include <winsock.h>
SOCKET s; WSADATA w;
//LISTENONPORT – Listens on a specified port for incoming connections //or data
int ListenOnPort(int portno)
{
int error = WSAStartup (0x0202, &w); // Fill in WSA info
if (error)
{
return false; //For some reason we couldn’t start Winsock
}
if (w.wVersion != 0x0202) //Wrong Winsock version?
{
WSACleanup ();
return false;
}
SOCKADDR_IN addr; // The address structure for a TCP socket
addr.sin_family = AF_INET; // Address family
addr.sin_port = htons (portno); // Assign port to this socket
//Accept a connection from any IP using INADDR_ANY
//You could pass inet_addr("0.0.0.0") instead to accomplish the
//same thing. If you want only to watch for a connection from a /
/specific IP, specify that //instead.
addr.sin_addr.s_addr = htonl (INADDR_ANY);
112
9.6. Summary
A socket is the mechanism that most popular operating systems provide to give
programs access to the network. It allows messages to be sent and received be-
tween applications (unrelated processes) on different networked machines.Socket
are characterized by their domain, type and transport protocol. Common domains
are:
113
For the environment for the socket to work, we need to include winsock.h and link
libws2_32.a to your project in order to use the API that are necessary for TCP/IP.
If this is not possible, use LoadLibrary() to load ws2_32.dll at runtime, or some
similar method.
Revision Questions
114
LESSON 10
Remove Method Invocation in C++
Learning outcomes
Upon completing this topic, you should be able to:
115
116
class Echo
{
public:
std::string echo(const std::string &msg) { return msg; }
};
int main()
{
int port = 50001;
RCF::RcfServer server(port);
server.bind<I_Echo, Echo>();
server.start();
return 0;
}
And the client:
#include <RCF/RCF.hpp>
RCF_BEGIN(I_Echo, "I_Echo")
RCF_METHOD_R1(std::string, echo, const std::string &);
RCF_END(I_Echo);
int main()
{
std::cout << RcfClient<I_Echo>("localhost", 50001).echo("my message");
return 0;
}
The Boost.Serialization library is used to serialize parameters and return values.
It handles standard types and containers automatically, and is easily extended to
user defined classes. It also allows us to serialize pointers, with proper handling of
polymorphic pointers and multiple pointers to single objects. Basic Usage There
are three basic steps to using this framework:
2. Use the RcfServer class to expose objects that implement the interface.
117
3. Use the RcfClient<> classes to invoke methods on the objects exposed by the
server. The interface definition macros are used as follows:
118
server.bind<Interface, Object>();
// ... or one object shared by all clients
Object object; server.bind<Interface>(object);
// tell the server to start listening for connections
server.start();
// ...
// the server will shut down automatically as it goes out of scope
}
The objects are statically bound to the corresponding interface; there is no need for
the object to derive from an interface class as is the case for traditional dynamic
polymorphism. Instead, the compiler resolves the interface at compile time, which
is not only more efficient, but also allows more flexible semantics.
The server can handle multiple simultaneous clients, even in single threaded mode,
and can be stopped at any time. The lifetime of objects exposed by the server is
determined by the number of current connections to the given object; once there
are no more live connections to the object, a timeout is set, and when it expires, the
object is deleted.
To make a client call, we instantiate the corresponding RcfClient<> template and
pass the server IP and port number to the constructor. When the first remote method
is called, the client then attempts to connect to the server, queries for the given ob-
ject, invokes the requested member function of the remote object, and then returns
the remote return value.
// define the interface
RCF_BEGIN(Interface, "Interface")
RCF_METHOD_R2(int, add, int, int);
RCF_END(Interface);
// ...
{ std::string ip = "localhost";
int port = 50001;
RcfClient<Interface> client(ip, port); // connect and call the add function
int sum = client.add(1,1); // connection closed as we exit scope
}
Should any exceptions arise on the server side while invoking the requested object,
an exception of type RCF::RemoteException will be propagated back to the client
119
and thrown.
Should any exceptions arise anywhere else on the server side, e.g., while serializing
arguments, then the server will forcibly close the connection, and the client will
throw an exception.
RCF will automatically handle a range of parameter types, including C++ primitive
types (int, double, etc.), std::string, STL containers, and pointers and references to
any of the previously mentioned types. Polymorphic pointers and references, and
multiple pointers to single objects are correctly handled as well.
Smart pointers are also supported (boost::shared_ptr, std::auto_ptr), and are the
safest way of passing polymorphic parameters.
In CORBA, one can tag a parameter as in, out, or inout, depending on which
direction(s) one wants the parameter to be marshaled.
In RCF, the marshaling directions are deduced from the parameter type, according
to the following conventions:
value:in
Pointer: in
Const reference: in
Nonconst reference: inout
Nonconst reference to pointer: out
To use user-defined types as parameters or return values, some additional serializa-
tion code is needed. What that code is depends on which serialization protocols
are being used; by default Boost.Serialization is used, and an example of passing a
user-defined type would look like the following:
struct MyStruct
{
int a;
int b;
int c;
double d;
std::string s; std::map <std::string, std::vector<std::string> > m;
template<typename Archive>
void serialize(Archive &archive, unsigned int version) { ar & a & b & c & d & s &
m; } };
RCF_BEGIN(MyInterface, "MyInterface")
120
121
122
int main()
{ ... // Connect to the server.
RMI::connect(...);
...
Point center(11, 22); // A RMI::Circle proxy (not the real // Circle object) is created
on the // client.
Circle circle("Circle", center, 33);
}
. The RMI::Circle proxy constructor communicates with the server, which in turn
remotely creates a Circle instance and returns an RMI reference to the instance.
When the circle proxy goes out of scope, it destroys the remote Circle object that it
created and represented.
The necessary steps for building an RMI-enabled distributed system are essentially
very similar to those required by the Java RMI. as follows.
2. Run these classes through the RMI code generator to generate declarations
for corresponding RMI::Widget and RMI::Circle proxies and the necessary
client/server infrastructure.
4. Write and compile the client application using generated RMI proxies.
The Java concept of dynamic class loading from the server is not supported. Un-
like Java (a programming environment), C++ (a programming language) does not
specify bytecode formats. That makes implementation of such a feature unrealistic.
123
124
returned (and arguments to remote methods are passed internally) by an RMI ref-
erence (proxy) or by value, using object serialization. Any data of any type can be
passed to or from a remote method as long as the data is a registered remote object
(having an RMI proxy generated for it), or a serializable object (having conversion
functions implemented for the type).
2. Locally (on the client or the server) and, therefore, returned or passed by
value. For the first option, you’ll need to generate an RMI::Point proxy in
the same way we did for Widget and Circle: // The original class
125
{
...
RMI::Point center(23,46);
RMI::Circle* circle = new RMI::Circle("new", center, 33);
}
This fragment will result in the following message exchange between the client and
the server:
1. The client creates an RMI::Point proxy and sends a request to create a remote
Point object.
2. The server sends the reply with the reference to the new Point object.
3. The client sends a request to create a remote Circle object passing the RMI::Point
reference as an argument.
4. The server sends the reply with the reference to the new Circle object.
5. The client sends a request to delete the remote Point object when center goes
out of scope.
6. The server sends the confirmation of successful deletion. This is quite a "con-
versation" for just two lines of code.
More so, for every proxy on the client, there is a real object on the server allocated
using operator new. Remotely allocating and destroying a small and transient Point
object is likely to be unnecessarily expensive performance-wise and resource-wise.
If creating Point objects locally and simply passing them by value.
{
...
Point center(23, 46);
RMI::Circle* circle = new RMI::Circle("new", center, 33);
}
The use of full type qualifications is to highlight a subtle change.
A Point instance is created instead of an RMI::Point proxy. The client-server dialog
shrinks considerably:
126
1. The client sends a request to create a remote Circle object passing a locally
created Point object by value as an argument.
2. The client receives the reply with the reference to the new Circle object.
10.6. Summary
The RMI namespace is open — add serialization routines for more types when the
need arises. Java RMI uses a technology called object serialization to transform an
object into a linear format. That technology essentially flattens an object and any
127
Revision Questions
E XERCISE 37. Discuss using a diagram how RMI works in C++ language.
E XERCISE 38. Explain the message exchange between the client and the server
in a RMI environment. .
E XERCISE 39. Write a simple program code to explain RMI in C++
E XERCISE 40. Compare the implementation of RMI in C++ and in Java lan-
guage.
Problem. Write a simple program code that uses a RMI mechanism with a user
defined type.
128
Solutions to Exercises
Exercise 2. this is the answer to the SECOND question Exercise 2
Exercise 6. this is the answer to the SECOND question Exercise 6
Exercise 10. this is the answer to the SECOND question Exercise 10
Exercise 26. this is the answer to the SECOND question Exercise 26
Exercise 30. this is the answer to the SECOND question Exercise 30
Exercise 34. this is the answer to the SECOND question Exercise 34
Exercise 37. this is the answer to the SECOND question Exercise 37
129