320 likes | 451 Views
Introduction to C++ Programming Module 5 C++ Stream I/O and Exception Handling. Yaodong Bi, Ph.D. Department of Computing Sciences University of Scranton (717)941-6108 bi@cs.uofs.edu. Outline. Module 1 - An Overview of C++ and OO Programming Module 2 - Classes and Operator Overloading
E N D
Introduction to C++ ProgrammingModule 5C++ Stream I/O and Exception Handling Yaodong Bi, Ph.D. Department of Computing Sciences University of Scranton (717)941-6108 bi@cs.uofs.edu
Outline • Module 1 - An Overview of C++ and OO Programming • Module 2 - Classes and Operator Overloading • Module 3- Inheritance and Dynamic Binding • Module 4 - Function and Class Templates • Module 5 - C++ Stream I/O and Exception Handling
Review of Module 4 • Function Templates • Function Template Definition • Function Template Instantiation • Template Explicit Specialization • Overloading Function Templates • Template Compilation Models • Class Templates • Class Template Definition • Class Template Instantiation • Members of Class Templates • Friend Declarations in Class Templates • Class Template Compilation Model
Outline of Module 5 • Exception Handling • Throwing an Exception and the Try Block • Catching an Exception • Exception Specifications • Advanced Topics • C++ Stream I/O • Input/Output of Built-in Types • Formats • Overloading the I/O Operators << and >> • File Input and Output • Condition States
// except.h #include <iostream.h> class CPopOnEmpty { public: CPopOnEmpty(int sz = 0); void Print() const; private: int m_nStackSize; }; class CPushOnFull { public: CPushOnFull(int sz = 0); void Print() const; private: int m_nStackSize; }; // except.cpp #include "except.h" CPopOnEmpty::CPopOnEmpty(int sz) :m_nStackSize(sz) { } void CPopOnEmpty::Print() const { cout << "Exception: Pop on empty stack!\n"; cout << "Stack Size = " << m_nStackSize << endl; } CPushOnFull::CPushOnFull(int sz) :m_nStackSize(sz) { } void CPushOnFull::Print() const { cout << "Exception: Push on Full stack!\n"; cout << "Stack Size = " << m_nStackSize << endl; } Example 1: Stack with Exceptions
// Stack.h const int maxSize = 10; class CStack { public: CStack(int sz = maxSize); CStack(const CStack&); CStack& operator= (const CStack&); void Push (int item); int Pop(); bool IsEmpty() const; bool IsFull() const; ~CStack(); private: int* m_Array; int m_nTop; int m_nSize; }; // stack.cpp #include <iostream.h> #include "Stack.h" #include "except.h" void CStack::Push(int item) { if (IsFull()) throw CPushOnFull(m_nSize); m_Array[++m_nTop] = item; } int CStack::Pop() { if (IsEmpty()) throw CPopOnEmpty(m_nSize); return m_Array[m_nTop--]; } bool CStack::IsEmpty() const { return (m_nTop < 0); } bool CStack::IsFull() const { return (m_nTop == m_nSize - 1); } CStack::~CStack() { delete [] m_Array; } Ex 1: Stack with Exceptions - cont’d
// stack.cpp -- cont’d CStack::CStack(int sz) { m_Array = new int[sz]; m_nSize = sz; m_nTop = -1; } CStack::CStack(const CStack& stack) { m_nSize = stack.m_nSize; m_nTop = stack.m_nTop; m_Array = new int[m_nSize]; for (int i=0; i<=m_nTop; i++) m_Array[i] = stack.m_Array[i]; } CStack& CStack::operator=(const CStack& stack) { if (&stack != this) { delete [] m_Array; m_nSize = stack.m_nSize; m_nTop = stack.m_nTop; m_Array = new int[m_nSize]; for (int i=0; i<=m_nTop; i++) m_Array[i] = stack.m_Array[i]; } return *this; } // class.cpp #include <iostream.h> #include "Stack.h" #include "except.h" void main() { CStack stack; try { for ( int i = 1; i < maxSize+3; i++) stack.Push(i*100); } catch (CPushOnFull pushExcept) { pushExcept.Print(); } // continued on next page Ex 1: Stack with Exceptions - cont’d
// class.cpp -- cont’d try { for (;;) cout << stack.Pop() << endl; } catch (CPopOnEmpty popExcept) { popExcept.Print(); } } The first for-loop tries to push three more items than the capacity of the stack. The second infinite for-loop pops until the stack is empty Exception objects class CPopOnEmpty; class CPushOnFull; The throw expression void CStack::Push(int item) { if (IsFull()) throw CPushOnFull(m_nSize); …. } The try - catch block try { for (;;) cout << stack.Pop() << endl; } catch (CPopOnEmpty popExcept) { popExcept.Print(); } Ex 1: Stack with Exceptions - cont’d
Exception Handling • Problem Statement • The designer of a library can detect run-time errors, but does not in general know what to do with them. The user of the library may know how to cope with such errors but cannot detect them. • Ex.: The user of a math library may not know when a expression may have a divide-by-zero operation. The math library can detect it and the user program may know how to handle it. • A Solution -- Exception handling • Exceptions provide a way for code that detects a problem from which it cannot recover to pass the problem on to some part of the system that might be able to recover. • C++ implementation on Exception handing • A function that finds a problem it cannot handle throws an exception, and its (direct or indirect) caller that can handle the problem catchesthe exception. • Ex.: The math library throws a divide-by-zero exception, the user program catches the exception and handles it.
Throwing an Exception and the Try Block • Throwing an Exception -- by the detector • Syntax: throw An_Exception; ex: throw 2; throw CPopOnEmpty(m_nSize); // call a constructor • An exception must be an object; it cannot be a class type. throw CPopOnEmpty; // illegal - not an object • The Try Block -- by the caller • The caller that can handle the exception executes the try block. try { // statements that can cause exceptions } catch (Exception_1) { // handle exception1 } …. catch (Exception_n) { // handler exception_n; }
Throwing an Exception and the Try Block • Control flow of the try block • If no exception occurs, the code in the try-block is executed and its handlers are ignored. The next statement after the last catch clause is executed. • If an exception is thrown, • the statements following the throw statement are ignored and the try-block exits (the stack is unwound.). • The catch clauses are checked in order. The first matched catch is executed. After a catch clause is completed, the program continues at the statement following the last catch in the list. • If no catch clause can catch the exception, the exception is passed to the next caller along the calling chain. • Once an exception is thrown, the program does not resume where the exception was originally thrown even after the exception is handled. • A try block introduces a local scope -- a variable declared within the block cannot be referred to outside the block, including the catch clauses. try { … int x; …} catch (exception) { … cout << x; // error: x is undefined …} • It is possible to include the entire body of a function in the try block. int main() try {// the body of main() … } catch (exception) { … }
Catching an Exception • Exception objects • The exception declaration of a catch clause can be either a type declaration or an object declaration. try { … } catch (int) {…} // type delcaration. try { … } catch (int x) {… cout << x; ...} // object delcaration. try { … } catch (CPopOnEmpty x) {… x.Print(); ...} // object delcaration. • An exception object is always created at the throw point. Type X; // a global variable int main () { try { ...throw X; } // an exception object is created and initialized with X catch (Type EX) { EX = Y;} // EX is copied from the exception object. The assignment // changes the local EX, not the exception object, not the global X catch (Type& EX) { EX = Y;} // EX is a reference to the exception object, not the global X // the assignment changes the exception object }
Catching an Exception • Stack Unwinding • When an exception cannot be handled, the search is continued to the calling function. This is continued until the exception can be handled or the program terminates. Ex: try {… foo(x); } void foo(x) void foobar() catch (Type1) { try { … foobar() } { …. { //handler Type1; catch (Type2) throw Type1(); ….} {…} } … } • As the stack being unwound, the local objects at each unwound level end and their destructors are called. In the above example, when throw Type() is completed, 1. all local variables in foobar() are destroyed with their destructors. 2. All local variables in foo() are destroyed with their destructors. • The Catch-all handler -- ellipsis. Ex: try {… } // should be used as the last catch clause. catch (…) { // enter for any type of exception }
Catching an Exception • Rethrow • A catch clause can pass the exception further up the list of function calls by re-throwing the exception. Ex: try {… foo(x); } catch (Type1 EX) { if (cannothandle(EX)) { throw; } // re-throw the exception ….} • The exception that is re-thrown is the original exception object. Type X; // a global variable int foo () { try { ...throw X; } catch (Type EX) { … EX = y; throw; } } int main () { try { …foo(); } catch (Type &EX1) { … EX1 = z; } } // EX1 references to the original except. object copied from the global X // it doesn’t reference to EX in foo(), not to the global X either.
Exception Specifications • An exception specification follows the function parameter list. class CStack { ... void Push (int item) throw (CPushOnFull); int Pop() throw (CPushOnFull); }; • An empty exception specification guarantees that the function doesn’t throw any exception. void foo(int item) throw (); // foo() will not throw any exception • If a function declaration does not specify an exception specification, it may throw exceptions of any type. void foo(int item) ; // foo() can throw an exception of any type • If a function throws an exception that is not in the specification list, the function unexpected() in the standard library is invoked, which in turn, by default, terminates the program. • MS VC++ 6.0 doesn’t seem to support exception specifications.
Advanced Topics: Grouping of Exceptions class Matherr { //… virtual void debug_print(); }; class Overflow: public Matherr { //… virtual void debug_print(); }; class ZeroDivide: public Matherr { //… virtual void debug_print(); }; class IntOverflow: public Overflow { //… virtual void debug_print(); }; int add(int x, int y) { // ….. if (overflow()) throw IntOverflow(); } void foo() { try { int i = add(INT_MAX, 2); } catch (Matherr& m) { m.debug_print(); // IntOverflow’s debug_print() } } • Grouping of Exceptions: Put exceptions into families
Advanced Topics: Resource Management class File_ptr { FILE* p; public: File_ptr(const char* n, const char* a) { p =fopen(n, a); } ~File_ptr() { fclose(p); } }; void use_file(const char* fn) { File_ptr f_ptr(fn, “r”); // use file }; f_ptr is a local object of use_file(). If an exception occurs in use_file(), the file pointed by f_ptr would be closed when use_file exits and File_ptr’s destructor is called automatically. class Acquire { File_ptr ff; Lock_ptr ll; public: Acquire(const char* n, const char* m) : ff(n, “r”),// acquire ff ll(m)//acquire ll { // constructor body } }; If an exception occurs in ll(m), the destructor of ff will be invoked. If the Acquire constructor’s body has an exception, the destructors of ff and ll will be invoked. ==> resources are released. • Resource Acquisition is initialization
Input/Output of Built-in Types • Input/Output classes • istream: input stream class • ostream: output stream class • iostream: bidirectional input/output class • Standard Input/Output objects • cin: an istream class object for standard input • cout: an ostream class object for standard output • cerr: an ostream class object for standard error output • The output operator <<. • It accepts argument of any of the built-in data types. cout << int_var << double_var; cout << “this is a string!\n” << char_ptr; cout << &int_var; // print the address of int_var in hexadecimal. cout << true << false; // print 1 and 0 respectively, by default. • Operator << associates left to right cout << x << y; // is equivalent to ( cout << x ) << y;
Input/Output of Built-in Types • The input operator >>. • It accepts argument of any of the built-in data types. cin >> int_var >> double_var; cin << char_var; // all white spaces are skipped • Operator >> associates left to right cin >> x >> y; // is equivalent to ( cin >> x ) >> y; • By default, the operator >> discards any intervening white space ( blank, tab, newline, formfeed, and carriage return). Input sequence: 123 1 b c d cin >> int_var >> ch1 >>ch2 >> ch3 >>ch4; cout << int_var<<ch1<<ch2<<ch3<<ch4; // prints 1231bcd • To process every input character including white spaces: while (cin.get(char_var)) cout.put(char_var); // read every char in input and print char by char
Formats • Integer outputs cout.setf(ios_base::oct, ios_base::basefield); cout << 1234; //print octal 2322 cout.setf(ios_base::dec, ios_base::basefield); cout << 1234; //print decimal 1234 -- decimal is the default • Floating-point output cout << 1234.56789; // default output: 1234.57 cout.setf(ios_base::scientific, ios_base::floatfield); cout << 1234.56789; // scientific format: 1.2345678e+003 cout.setf(ios_base::fixed, ios_base::floatfield); cout << 1234.56789; // fixed point format: 1234.567890 cout.setf(0, ios_base::floatfield); // reset default cout.precision(8); // set precision to 8 cout << 1234.56789; // print: 1234.5679 • Above print results were generated with MS VC++ 6.0 and its standard iostream.
Formats • Output fields cout.width(4); // only affect the immediate << operation cout << 12; //print with two space in front of 12 cout.width(4); cout.fill(‘#’); cout << 12; //print ##12 cout.width(0); // print as many as needed • Field adjustment cout.width(4); cout.fill(‘#’); cout.setf(ios_base::left, ios_base::adjustfield); // left cout.width(4); cout << -12; // print -12# cout.setf(ios_base::right, ios_base::adjustfield); // right -- default cout.width(4); cout << 12; // print ##12 cout.setf(ios_base::internal, ios_base::adjustfield);// between sign and # cout.width(4);cout << -12; // print -#12 • Above print results were generated with MS VC++ 6.0 and its standard iostream.
Formats • Manipulators -- must #include <iomanip> cout << x << flush << y << flush; // explicitly request that the buffer be flushed. cin >> noskipws >> x; // don’t skip white spaces. cout << 1234 << “, “ <<hex<< 1234 <<“, “ << oct << 1234; // prints 1234, 4d2, 2322. cout << setw(6) << setfill(‘$’) << 12; // prints $$$$12. • When using manipulators that don’t take arguments, don’t add (). cout << hex() << 12; // error: hex doesn’t take arguments • make sure #include <iomanip> in your program • Users may define their own manipulators • The manipulators shown here are only some examples. For more comprehensive descriptions, read Lippman’s and Strousptrup’s books. • Above print results were generated with MS VC++ 6.0 and its standard iostream.
Overloading the I/O operators << and >> • General skeleton of an overloaded output operator ostream& operator<<(ostream& os, const ClassType &obj) { // actual output of the members. os << // … … // return ostream object return os; } • General skeleton of an overloaded input operator istream& operator>>(istream& is, ClassType &obj) { // actual input to the members. os >> // … … // return istream object return is; }
Overloading the I/O operators << and >> • An Example class complex { friend ostream& operator<<(ostream& os, const complex &obj); friend istream& operator>>(istream& is, complex &obj); Private: int real, image; }; ostream& operator<<(ostream& os, const complex &obj) { os << “< “ <<obj.real <<“, “ <<obj.image<<“ >\n” ; return os; } istream& operator>>(istream& is, complex &obj) { is >> obj.real >> obj.image; return is; } int main() { complex cmplx; cin >> cmplx; cout << cmplx; return 0; }
#include "stdafx.h" #include <iostream.h> class base { public: virtual ostream& put(ostream& s) {return s <<"Base\n";} }; class derived:public base { public: virtual ostream& put(ostream& s) {return s <<"Derived\n";} }; ostream& operator<<(ostream& s, base& b) { return b.put(s); } int main(int argc, char* argv[]) { base bs; derived dr; cout << bs << dr; return 0; } Virtual output functions The ostream members are not virtual. Since the exact type of a subclass may not be known yet, correct output cannot be achieved simply by defining a << for each new type. Implementation Design a virtual put function in the base and subclasses. virtual ostream& put(ostream& s); Call the virtual function in << from a reference to the base ostream& operator<< (ostream& s, base& b) { return b.put(s); } << doesn’t need to be a friend of the classes in this example. Overloading the I/O operators << and >>
File Input and Output • To use file input and output, include #include <fstream> using namespace std; • To open a file for output only. ofstream outfile(“filename”, ios_base::out); ofstream outfile(“filename”); // output is default // when the file does not exist, it would be created. // if an file is opened for output, all data in the file is discarded. if (!outfile) //open failed. { cerr << “file cannot be opened!\n” exit(-1); } • ofstream is derived from ostream. All the ostream operations can be applied to ofstream objects. char ch = ‘ ‘; outfile.put(‘1’).put(‘)’).put(ch); outfile<<“1+1=“ << 1+1 << endl; // insert 1) 1+1 = 2 to outfile. • To open a file for append. ofstream outfile(“filename”, ios_base::app);
File Input and Output • To open a file for input only. ifstream infile(“filename”); if (!infile) { cerr <<“input file open failed\n”; exit(-1); } infile >> int_var; while (infile.get(ch)) cout.put(ch); • An fstream object can open a file for either input and output. fstream file; file.open(“filename”, ios_base::in); // open the file for input file >> wd; file.close(); file.open(“filename”, ios_base::app); // open the same file for output file << endl << wd << endl; file.close(); • An fstream object can open a file for both input and output. fstream file; file.open(“filename”, ios_base::in | ios_base::app);
Condition States • Condition flags. • eof(): return true if end-of-file has been encountered. if (cin.eof() ) { // ok, input complete.} • bad(): return true if an invalid op. has been attemped -- file corrupted. if (cin.bad() ) { // file corrupted in some undefined way.} • fail(): return true if an o.p has been unsuccessful. Ex. Invalid input format ifstream infile(“filename”); if (infile.fail() ) { // file open failed.} • good(): return true if previous operation was successful - file in good cond. If (infile.good() ) { // continuing processing the file.} • Two methods for modifying the condition states. • clear() resets the condition state to an explicit value cin.clear(ios_base::goodbit); // reset cin to good condition • setstate(): add a condition tot he existing condition of the object. if ((ch = is.get()) != ‘<‘) cin.setstate(ios_base::failbit); // if the current char is not ‘<‘, mark the fail state. • To set multiple states, cin.setstate(ios_base::badbit | ios_base::failbit); // set both bad state and fail state.
Summary of Module 5 • Exception Handling • Throwing an Exception and the Try Block • Catching an Exception • Exception Specifications • Advanced Topics • C++ Stream I/O • Input/Output of Built-in Types • Formats • Overloading the I/O Operators << and >> • File Input and Output • Condition States
Advice • Don’t use exceptions where more local control structures will suffice. • Use the “resource acquisition is initialization” to manager resources. • Not every program needs to be exception safe. • Avoid throwing exceptions from copy constructors. • Avoid throwing exceptions from destructors. • Throw an exception to indicate failure in a constructor. • Develop an error-handling strategy early in the design. • Overload << and >> for user-defined types with values that have meaningful textual representations. • Remember that by default >> skips white spaces • Remember to #include <iomanip> when using standard manipulators. • Remember that the width manipulator applies to the following I/O operation only. • Define virtual input/output functions.
Programming Assignments • Exception -- CDate • Modify the CDate example so that it throws exceptions when a value is out of valid range. • Group the exceptions in CDate into a class hierarchy. Use virtual functions to print offensive values (year, month, or day). • Write a main() to test those exceptions. • Exception -- An array class • Design an array class that throws OutOfRange when the subscript is of range. OutOfRange can print the size of the array and the offensive subscript. • Overload Operators << and >> • Design virtual input and output functions for the CPerson-CManager-CEngineer inheritance example. • File Input/output • Design a program that counts the number of words and the number of white spaces in the input file.