EXCEPT1 id 2053264 Nieznany


Document :: Exceptions Version 1

Author :: Kris Erickson

From "The Bits" Website

http://www.cbuilder.dthomas.co.uk

email to : kson@istar.ca

Legal Stuff

This document is Copyright ©Kris Erickson May 1997

This release of the document ©The Bits They Forgot May 1997

You may redistribute this file FREE OF CHARGE providing you do not charge recipients anything for the process of redistribution or the document itself. If you wish to use this document for educational purposes you may do so free of charge.

However, If you wish to use all or part of this document for commercial training or commercial publication you must contact the author as charges may apply.

You may not receive **any** moneys for this document without the prior consent of the author.

No liability is accepted by the author(s) for anything which may occur whilst following this tutorial

Greetings all. This article will be a little different as it is more about C++ than it is about Builder.

This is part one of two. This article deals with generalized excpetions, and the follow up will deal with exceptions in C++ Builder. There aren't any real examples designed for Builder, but I recommend you play around with them as console applications (or in BC++ 4.0+, or Visual C++

4.0) to understand how exceptions work. I wouldn't have written this aritcle if I knew of any good sources on exceptions, but at least all the books I have either don't mention them or are cryptic or incomplete on how to use them. Anyhow, I hope you learn a little about exceptions from this article, and maybe it will serve as a little refresher (or introduction) to Object Oriented Programming techniques (after all with Builder, we are now all Object Oriented Programmers.) Enjoy,

Kris...

Colour Coding:

Information displayed in this colour is information provided by my copy of BCB.

Information displayed in this colour is information you need to type.

Information displayed in this colour is information which is of general interest.

Code displayed in this colour is not meant to be typed in, but only included only a an example.

Exceptions are one of the later additions to the C++ language (along with Templates), and appear to be an important addition to Object oriented programming in general (they play an important role in JAVA for example). Builder uses them extensively, and if you want to understand how builder works (the fact that every VCL program you create in Builder is wrapped in a try... catch exception), and if you wish to builder robust applications of your own, you really should learn how to use exceptions.

I must admit that I was a little reticent to use them myself, after all I had had assertions and such pounded into my head from day one in college (not to say that there is no longer a place for assertions, just that exceptions are much more robust). To me creating robust code meant have all procedures return a TRUE or FALSE, and all functions return an invalid value if they failed.

Well, exceptions are much nicer than this, and once you get the hang of them they are a lot simpler (and cleaner) than the kind of code I used to create. There are some tricky situations if you allocate memory in your exception class, but if you use nice robust objects (like AnsiString) you really don't have to worry about that (and nicely enough in Builder, pretty well all of the exceptions have been created for you!)

Before we jump into Builder exceptions, let's get grounded in the world of exceptions for C++.

Suggested as an addition to the C++ language in April 1990 by Andrew Koenig and Bjarne Stroustrup in the article Exception Handling for C++, it was adopted by the ANSI C++ standards comitte in November of 1990. However a lot of compilers didn't support it until quite recently.

Borland didn't support it until BC++ 4.0, and Microsoft didn't support it until VC++ 4.0 either (somebody correct me if I am wrong on these...) So to test out the non VCL you will either need Borland C++ 4.0+, Visual C++ 4.0+ (it must be noted that all the programs here are really just code snippets, and won't actually compile without a few changes made, however if you really wish to learn standard C++ exceptions you should play around with them) or make a console application in Builder (actually running these applications as a console application in a debugging session of Builder is a little different as Builder will stop the application as soon as it runs into an exception and you will have to step through the program to have it react to any of the "catch"

code, or simply run the program from outside the Builder debugging envirnment).

Back in the good old days if we wanted to load some data from a file we would create a procedure to do this (or even better wrap the data in an object that would be able to load and save itself) that would return TRUE on success, and FALSE on failure... I.e.

int main(void)

{

// Code goes here ...

if (!LoadData(Filename))

{

cerr << "An error occurred loading the data... Exiting!" << endl; exit(-1);

}

// Continue program ...

}

BOOL LoadData(char* filename)

{

// code here ....

if (!fopen(filePointer,filename))

{

return false;

}

// more code ....

if (feof(filePointer) && dataNotFinished)

{

fclose(filePointer);

return false; // Note enough data to fill object

}

// more code ....

if (current_data < 0 or current_data > 5000)

{

fclose(filePointer); // Invalid data

return false;

}

// more code ....

return true;

}

This probably looks very familiar to anyone who has done any amount of programming (whether it be in C, C++, or whatever language). Now you run the program and everything works fine the first twenty times, but the twenty first time a while running the program you get the following message "An error occurred loading the data... Exiting!". You don't know what error, so you go back and change your program to return an integer rather than a boolean, and you create 16

different error codes that are all refer to different errors. You main has something like this in it now...

loadResult = LoadData(FileName);

if (loadResult > 0)

{

switch (loadResult)

{

case 1 : cerr << "Error opening file." << endl; break;

case 2 : cerr << "Insufficient amount of data." << endl; break;

// ... etc.. etc..

Now this has created a much more robust program, but it is kind of a syntactical nightmare...

First of all your LoadData() function has sixteen different return statements (and one of the rules of programming I was taught was that each function should have one point of entry, and one point of exit, I know that I personally break this rule a lot, but I really do try not to.) To solve this problem, all we have to do is turn to the beauty of exceptions. When can create code that has almost exactly the same effect as above, but is a little more robust very easily. Our main now looks like...

int main(void)

{

// Code goes here ...

try

{

LoadData(Filename);

}

catch (int loadResult)

{

switch (loadResult)

{

case 1 : cerr << "Error opening file." << endl;

break;

case 2 : cerr << "Insufficient amount of data." << endl; break;

// ... etc.. etc..

}

}

// Continue program ...

}

and our LoadData routine would look like

void LoadData(char* filename)

{

// code here ....

if (!fopen(filePointer,filename))

{

throw 1;

}

// more code ....

if (feof(filePointer) && dataNotFinished)

{

fclose(filePointer);

throw 2;

}

// more code ....

if (current_data < 0 or current_data > 5000)

{

fclose(filePointer); // Invalid data

throw 3;

}

// more code ...

return;

}

Now, this is a small improvement, but I doubt I could convince anyone to buy into exceptions if this is all they did. However, I using this just as a simple example. The module throws an exception, and the client catches it. The process is as follows, once an exception is thrown the program looks for anything that is willing to catch it. If nothing catches it you get an ugly message from the runtime engine. If it reaches a catch statement it determines whether or not the catch statement was designed to catch that type of exception. In the above example we are throwing an integer exception, and the catch statement is designed to catch integers so the program will hand control over to the catch statement and continue execution. The whole process is very clever, and it will unwind as many layers of call statements as it needs to reach an appropriate handler.

Like I said, if all exceptions could throw was integers, then we wouldn't find them very appealing.

Of course you could throw and enumerated error type that would make much more sense to the reader of the code, but still that produces a lot of limitations. Let's try throwing a null termated string instead...

int main(void)

{

// Code goes here ...

try {

LoadData(Filename);

}

catch (char* errorMessage)

{

cerr << errorMessage << endl;

throw; // Note: we could place an exit(1) here, or something, but this

// will do the same thing by propagiting the exception, the

// C++ run-time engine will produce a nice error for the

// user that may or may not be meaningful (but they will

// also have the message we sent them...

}

// Continue program ...

}

void LoadData(char* filename)

{

// code here ....

if (!fopen(filePointer,filename))

{

throw "Couldn't open file!";

}

// more code ....

if (feof(filePointer) && dataNotFinished)

{

fclose(filePointer);

throw "Not enough data to fill object!";

}

// more code ....

if (current_data < 0 or current_data > 5000)

{

fclose(filePointer);

throw "Invalid data!";

}

// more code ....

return;

}

Now are we beginning to see why this is such a nice and powerful tool? It also helps to create self documenting code, which is an added bonus. But it doesn't end there, through the magic of object oriented programming we can create exception objects and throw them as well. These Exception Objects can be much more complicated objects that can carry a lot more data. In fact, they can be polymorphic objects that can carry code that can be executed when the exception occurs (if you catch a base class, that class will catch any objects inherited from the base class. You have to be careful with this since, if you want to catch the inherited class you have the place that catch statement before the catch statement for the base class). Now all of this polymorphism and OOP can get a little complicated, so lets back up the programmers express and just create a simple exception object.

class ExceptionObject

{

// Note: The string class I am using here is (a close

// realative to AnsiString), but any string class which has a char*

// constructor, and overloads the '<<' operator for output will work.

public:

// Simple constructor. Uses syntactic sugar (initialization,

// whatever) to initilize data members.

ExceptionObject(const string &Module,int LineNo,const string &Error)

: _Module(Module),_LineNo(LineNo),_Error(Error) {}

// Always make your destructors virtual if you are going to

// inherit from the object...

virtual ~ExceptionObject () {}

// Simple accessors...

const string& Module() const { return _Module;}

int LineNo () const { return _LineNo;}

const string& Error() const { return _Error;}

// Right now this is a pure virtual function (it doesn't do anything

// but later versions might...)

const virtual void HandleException() {}

private:

string _Module, _Error;

int _LineNo;

};

Here we have a very simple exception object. We have created a virtual destructor because if there is any dynamic data in any of the descended classes those destructors have to be called first, and won't be unless the destructor for the base class is virtual (sorry if I am repeating obvious stuff here, but sometimes people are a little unsure of more advanced OOP practices, especially how they relate to C++). The three accessors are there, but we don't need any mutators since we are only going to initialize the data when we instantiate the object. We've also created a pure virtual function HandleException that may be used later (ok, it will be used later) by an object that inherits from ExceptionObject. Pure virtual functions are simple functions that contain no functionality and are only left there for the purposes of polymorphism (whoops I'm venturing back into this topic again...) Now how do we use this Exception Object, well I'll start of with an even simpler example than before (because I want to use the previous example in the inherited ExceptionObject).

int main (void)

{

// .. code goes here

try

{

float Total = DivModule (13,0) ; // may throw excpetion

}

catch(const ExceptionObject& Ex)

{

cout << "Module: " << Ex.Module() << "\nLine No: " << Ex.LineNo()

<< "\nError: " << Ex.Error () << endl; throw;

}

// .. code goes here

}

float DivModule(float numerator, float denominator)

{

if (denominator == 0)

{

throw ExceptionObject("DivModule",132,"Division by Zero.");

}

else

{

return numerator/denominator;

}

}

Ok, that was nice and simple. By now you should have a pretty good handle on how exceptions work. One last little trick and we'll get to what this has to do with Builder... Let's create an FileException object that is based on ExceptionObject only lets have the handler actually do something (say, close the file.).

class FileException : public ExceptionObject

{

public:

FileException(const string &Filename,const string &Module, int LineNo, const string &Error) : Exception(Module, LineNo, Error), _Filename(Filename) {}

const string& Filename () {return _Filename;}

cosnt void HandleException() { cerr << "Closing file: " << _Filename

<< endl;

fclose(_Filename);}

private:

string _Filename;

};

Now there are two ways that we can use this FileException object, the simple way is to try and catch several errors like this:

int main (void)

{

try

{

LoadData (Filename);

}

catch (const FileException& Ex)

{

cout << "File error: " << Ex.Error() << ". Occurred while accessing file:

"

<< Ex.Filename() << "\nIn module " << Ex.Module() << ", line: "

<< Ex.LineNo() << ".\n";

}

catch (const ExceptionObject& Ex)

{

cout << "Error " << Ex.Error() << ".\nOccurred in module " << Ex.Module()

<< ", line: " << Ex.LineNo() << ".\n"; }

catch (...)

{

cout << "Exception occurred that was not a member of ExceptionObject"

<< endl;

}

}

If an exception was thrown the program first checks whether it was a FileException object, and then if it isn't it is going to check if it was an ExceptionObject and then it is going to do the default handling (notice that the order here is VERY IMPORTANT, if you place catch (...) first you are always going to get the "Exception occurred that was..." message. Also as I said earlier,



any base class will happily catch the exception for its descendant classes). The other way this same form can be written is to use the power of polymorphism and only catch the base class...

int main (void)

{

try

{

LoadData ();

}

catch (const ExceptionObject& Ex)

{

cout << "Error " << Ex.Error() << " occurred in module " << Ex.Module()

<< ", line: " << Ex.LineNo() << ".\n"; Ex.HandleException();

}

}

This is a neat little way to handle errors, and uses the strengths of C++'s polymorphism. Now that we have a fairly good understanding of Exceptions, we can see in the next tutorial how we can use them in C++ Builder.

You really should play around with exceptions and become familiar and confident with them. To that end I have included 4 compilable versions of some of the sample programs included here.

They don't do anything (most of them have the excpetions thrown because of tests of 1 and 0), but they do demonstrate how excpetions work to a good degree. I compiled them and ran them in Borland C++ 4.52, but they should easily compile in any envirnment that supports exceptions.

Try changing them, extending the ExceptionObject class and building new classes based on ExceptionObject. Once you become familiar with exceptions you will find that they will cut your development (and bug squashing) time down considerably, and more importantly maintenance with be aided to new extents (it is much easier adding to programs that have become unfamiliar due to the passage of time where bugs are instantly documented with nice exceptions). One warning about exceptions, you may be tempted to use their power as a more powerful form of break, or return: don't! They should only be used for documenting errors, although they may work in other roles DO NOT base trick coding around them. It will cause you more headaches than it is worth (and yes I do speak from experience...) The author would appreciate any comments on this article, and would like to thank the authors of all of the copious letters he has recieved for the previous articles... Thank you, I have learned a lot from all of your insightful comments, and criticisms.

kson@istar.ca

No liability is accepted by the author(s) for anything which may occur whilst following this tutorial







Wyszukiwarka

Podobne podstrony:
CISAX01GBD id 2064757 Nieznany
SGH 2200 id 2230801 Nieznany
111003105109 stress id 2048457 Nieznany
CIXS201GBD id 2064760 Nieznany
TOCEL96GBB id 2491297 Nieznany
1078 2 FEA209544 128UEN A id 22 Nieznany
McRib(r) Sandwich id 2201097 Nieznany
BD V600 L3 C A3 V1[1] 1 id 2157 Nieznany
DOC0534 id 2032985 Nieznany
8 17 id 2009842 Nieznany
REKAN02GBBT id 2491218 Nieznany
cialo albatros id 2035175 Nieznany
[17] FR540NT010 id 2085454 Nieznany
RO7503GBDT id 2491245 Nieznany
VOLUP98GBD id 2134841 Nieznany
cienie w raju rebis id 2036016 Nieznany

więcej podobnych podstron