Exceptions and Exception Handling


When you develop applications, you have to write both the code that solves a particular problem and the code that checks for errors. Traditionally, the error-handling code is based around the if statement. The if statement is often used to test user input and function results. In simple cases, the if statement may be enough, but in GUI applications, where users have complete freedom, an error may occur anytime and anywhere. Using only the if statement to safeguard the application is not the best idea.

The exception handling mechanism is the best way to catch and respond to errors. When an error occurs in a Delphi application, the application automatically raises an exception. An exception is an object that describes the error that occurred. Raising an exception simply means that the application creates an exception object that describes the error in more detail.

If we don't handle the exception (if we don't write code that catches the exception), the application does it automatically. Usually, the application handles the exception by displaying a message box that notifies the user that an error occurred. For instance, if you pass a string that contains characters that can't be converted to a numerical value or an empty string to the StrToInt function, the function raises an exception (see Figure 13-1).

image from book
Figure 13-1: An exception handled by the application

To handle an exception raised by the StrToInt function, we have to put the call to the StrToInt function into a protected block. A protected block is a block that can respond to a certain exception. In Delphi, a protected block looks like this:

try   statement(s); except end;

In C++, a protected block looks like this:

try { } catch(...) { }

Statements that might raise an exception are placed in the try block, and any code that handles the exception is placed in the exception handler. The exception handler part of the protected block starts with the reserved word except in Delphi and the reserved word catch in C++.

If you pass a valid string to the StrToInt function and no exceptions occur, only the code in the try block is executed. The code in the exception block is only executed if a statement inside the try block raises an exception.

The following two examples show how to call the StrToInt function and catch any exception that might be raised by it. Listing 13-1A shows how to catch exceptions in a Delphi application, and Listing 13-1B shows how to catch exceptions in a C++Builder application.

Listing 13-1A: Catching an exception in Delphi

image from book
procedure TForm1.Button1Click(Sender: TObject); var   x: Integer; begin   try     x := StrToInt(Edit1.Text);   except     ShowMessage('An error occurred.');   end; end;
image from book

Listing 13-1B: Catching an exception in C++

image from book
void __fastcall TForm1::Button1Click(TObject *Sender) {    int x;    try    {       x = StrToInt(Edit1->Text);    }    catch(...)    {       ShowMessage("An error occurred.");    } }
image from book

image from book
Figure 13-2: An exception handled by our exception handler

Handling Specific Exceptions in Delphi

Now, let's try to create a simple calculator that only enables you to divide two numbers. The user interface of the calculator is displayed in Figure 13-3.

image from book
Figure 13-3: The simple calculator

To divide the values entered into the TEdit components we have to write code that first converts both numbers to integers and then divides them. This code can easily raise two exceptions. It can raise the EConvertError exception when a value in one of the TEdit components can't be converted, or it can raise the EDivByZero exception when you try to divide the first number by 0.

Listing 13-2: Dividing two values

image from book
procedure TForm1.Button1Click(Sender: TObject); var   Num1: Integer;   Num2: Integer; begin   try     Num1 := StrToInt(Edit1.Text);     Num2 := StrToInt(Edit2.Text);     ShowMessage('Result: ' + IntToStr(Num1 div Num2));   except     ShowMessage('You can''t divide these values.');   end; end;
image from book

Although you can write exception handlers that catch all exceptions, you should try to handle specific exceptions. The ability to handle a specific exception is provided by the reserved word on. The syntax of the reserved word on is:

on SomeException do HandleTheException;

The on-do construct can only be used inside the exception handler:

try   statements(s); except   on Exception do HandleException;   on AnotherOne do HandleThisOne; end;

If possible, you should use the on-do construct to handle different exceptions in their own way. For instance, you can handle the EConvertError by displaying a message that displays the error, and you can handle the EDivByZero exception by telling the user that the second number can't be 0 and automatically changing it to 1. Listing 13-3A shows how to handle specific exceptions in Delphi. Listing 13-3B shows how to handle specific exceptions in C++.

Listing 13-3A: Handling specific exceptions

image from book
procedure TForm1.Button1Click(Sender: TObject); var   Num1: Integer;   Num2: Integer; begin   try     Num1 := StrToInt(Edit1.Text);     Num2 := StrToInt(Edit2.Text);     ShowMessage('Result: ' + IntToStr(Num1 div Num2));   except     on EConvertError do       ShowMessage('One of the values is invalid.');     on EDivByZero do     begin       ShowMessage('This value can''t be 0.');       Edit2.Text := '1';       Edit2.SetFocus;     end; // on EDivByZero   end; end;
image from book

When you use the on-do construct to handle specific exceptions, you should also add code that deals with errors you know nothing about. To handle exceptions that you haven't handled specifically, you can add an else part to the exception handler.

try   statement(s); except   on Exception do HandleIt;   on AnotherOne do HandleThisOne;   else     HandleAllOtherExceptions; end;

Handling Specific Exceptions in C++

To handle specific exceptions in C++, you have to write one or more catch blocks after the try block. When you use catch to catch a specific exception, you can't just specify the exception class as you can in Delphi. In C++, when you catch an exception you also have to specify the exception object and catch it by reference. In Delphi, you can catch an exception with or without catching the exception object. To see how to use the exception object in Delphi, see the "Using the Exception Object" section later in this chapter.

So, to handle specific exceptions in C++, you have to write this:

try { } catch(ExceptionClass &Object) { } catch(AnotherExceptionClass &Object) { } /* if you want to catch other exceptions write catch(...) at the end */ catch(...) { }

Listing 13-3B: Handling specific exceptions in C++

image from book
void __fastcall TForm1::Button1Click(TObject *Sender) {    int Num1;    int Num2;    try    {       Num1 = StrToInt(Edit1->Text);       Num2 = StrToInt(Edit2->Text);       ShowMessage("Result = " +  IntToStr(Num1 / Num2));    }    catch(EConvertError &e)    {       ShowMessage("One of the values is invalid.");    }    // instead of EDivByZero, division raises an    // EAccessViolation exception in a C++Builder application    catch(EAccessViolation &e)    {       ShowMessage("This value can't be 0!");       Edit2->Text = "1";       Edit2->SetFocus();    } }
image from book

Reraising Exceptions

When an error occurs, an exception object instance is created. When this exception is handled, the exception object is automatically freed. If you don't want to or don't know how to handle a specific exception, you should let Delphi handle it. To do that, you have to reraise the exception, that is, recreate the exception object instance. To reraise the exception in Delphi, you have to use the reserved word raise. In C++, you have to use the reserved word throw.

For instance, the following exception handler only handles the EConvertError exception. If any other exception is raised, the exception handler reraises it. This way the exception remains after the exception handler finishes, and another exception handler, usually the default one, has to handle it. Listing 13-4A shows how to reraise an exception in Delphi and Listing
13-4B shows how to reraise an exception in C++.

Listing 13-4A: Reraising exceptions in Delphi

image from book
procedure TForm1.Button1Click(Sender: TObject); var   Num1: Integer;   Num2: Integer; begin   try     Num1 := StrToInt(Edit1.Text);     Num2 := StrToInt(Edit2.Text);     ShowMessage('Result: ' + IntToStr(Num1 div Num2));   except     { Handle the EConvertError exception }     on EConvertError do       ShowMessage('One of the values is invalid.');     { Reraise all other exceptions. }     else raise;   end; end;
image from book

Listing 13-4B: Reraising exceptions in C++

image from book
void __fastcall TForm1::Button1Click(TObject *Sender) {    int Num1;    int Num2;    try    {       Num1 = StrToInt(Edit1->Text);       Num2 = StrToInt(Edit2->Text);       ShowMessage("Result = " +  IntToStr(Num1 / Num2));    }    catch(EConvertError &e)    {       ShowMessage("One of the values is invalid.");    }    catch(...)    {       Caption = "Something wrong happened.";       // reraise the exception       throw;    } }
image from book

So, if an EConvertError exception is raised, the exception handler handles it itself, and if any other exception occurs, like EDivByZero or EAccess- Violation, the exception handler reraises the exception and passes it to another handler (see Figure 13-4).

image from book
Figure 13-4: Local handler handling EConvertError and the default handler handling the reraised exception

Raising Exceptions

The reserved word raise is also used to raise exceptions. To raise an exception in Delphi, use the reserved word raise followed by an exception object instance. The exception object instance is usually a call to the exception constructor.

The syntax for raising an exception typically looks like this:

raise ExceptionClass.Create('Error message');

To raise an exception in C++, use the reserved word throw, also followed by a call to an exception's constructor:

throw ExceptionClass("Error message");

You can, for instance, create a custom version of the StrToInt function that raises the EConvertError exception with customized error messages when the string can't be converted. Listing 13-5A shows the Delphi version of the function and Listing 13-5B shows the C++ version.

Listing 13-5A: Raising exceptions in Delphi

image from book
unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,   Dialogs, ExtCtrls, StdCtrls; type   TForm1 = class(TForm)     procedure Button2Click(Sender: TObject);   private     { Private declarations }   public     { Public declarations }     function CustomStrToInt(const s: string): Integer;   end; var   Form1: TForm1; implementation {$R *.dfm} function TForm1.CustomStrToInt(const s: string): Integer; var   ErrorCode: Integer; begin   Val(s, Result, ErrorCode);   if ErrorCode <> 0 then   begin     if s = ''  then       raise EConvertError.Create('An empty string can''t be used here.')     else       raise EConvertError.Create('Hello? You can''t convert "' +         s + '" to an integer!');   end; end; procedure TForm1.Button2Click(Sender: TObject); var   Num1: Integer;   Num2: Integer; begin   Num1 := CustomStrToInt(Edit1.Text);   Num2 := CustomStrToInt(Edit2.Text);   ShowMessage(IntToStr(Num1 div Num2)); end; 
image from book

image from book
Figure 13-5: The EConvertError exception raised in the CustomStrToInt function

Listing 13-5B: Raising exceptions in C++

image from book
int __fastcall TForm1::CustomStrToInt(const AnsiString s) {    int Result;    if(s == "0")       return 0;    else {       if(s == "")         throw EConvertError("An empty string can't be used here.");       else {         // atoi returns 0 if it fails to convert the str to int         Result = atoi(s.c_str());         if(Result == 0)            throw EConvertError("Hello? You can't convert \"" +               s + "\" to an integer!");       }       return Result;    } }
image from book

Using the Exception Object

The on-do construct enables us to temporarily acquire the actual exception object by using the following syntax:

on Identifier: Exception do HandleIt;

The identifier is usually the capital letter E. When you acquire the exception object, you can use it like any other object and access its properties and methods. The only thing that you really must not do is destroy the exception object because exception objects are automatically managed by the exception handler.

Listing 13-6: Using the exception object

image from book
procedure TForm1.Button1Click(Sender: TObject); var   x: Integer;   y: Integer; begin   x := 20;   y := 0;   try     Caption := IntToStr(x div y);   except     on E: EDivByZero do       ShowMessage('Exception: ' + E.ClassType.ClassName +         #13 + 'Exception Message: ' + E.Message);   end; end;
image from book

image from book
Figure 13-6: Using the exception object

Creating Custom Exceptions in Delphi

Creating a custom exception is very simple and not that different from creating a custom class. Custom exceptions should descend from the Exception class or another descendant of the Exception class. The names of exception classes, by convention, always start with the capital letter E.

type   EMyException = class(Exception);

Listing 13-7A shows how to raise and catch a custom exception in Delphi. Listings 13-7B and 13-7C that follow in the next section show how to raise and catch the same custom exception in C++.

Listing 13-7A: Working with a custom exception

image from book
unit Unit1; interface uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,   Dialogs, StdCtrls; type   ENoUpperCaseLetters = class(Exception);   TForm1 = class(TForm)     Edit1: TEdit;     Button1: TButton;     Label1: TLabel;     procedure Button1Click(Sender: TObject);   private     { Private declarations }   public     { Public declarations }     function CountUpperCase(const s: string): Integer;   end; var   Form1: TForm1; implementation {$R *.dfm} function TForm1.CountUpperCase(const s: string): Integer; var   ch: Char; begin   Result := 0;   for ch in s do     if ch in ['A'..'Z'] then Inc(Result);   { if there are no uppercase letters, raise the exception }   if Result = 0 then     raise ENoUpperCaseLetters.Create('No uppercase letters in the string.'); end; procedure TForm1.Button1Click(Sender: TObject); var   Cnt: Integer; begin   try     Cnt := CountUpperCase(Edit1.Text);     Caption := IntToStr(Cnt) + ' uppercase letter';     if Cnt > 1 then Caption := Caption + 's';   except     on E: ENoUpperCaseLetters do       Caption := E.Message;   end; end; end.
image from book

image from book
Figure 13-7: Working with a custom exception

Creating Custom Exceptions in C++

Creating a custom exception in C++ is only a bit more complicated than it is in Delphi. To create a custom exception, you have to derive a new class from Exception and create a constructor that accepts an AnsiString message and passes it to the Exception's constructor:

class EMyException: public Exception { public:    EMyException(const AnsiString Msg) : Exception(Msg) {}; };

The : Exception(Msg) part of the constructor is known as the initializer list. In this case, it simply passes the Msg parameter to the Exception class's constructor.

Listings 13-7B and 13-7C show how to count uppercase letters in C++ and how to raise and catch the ENoUpperCaseLetters custom exception.

Listing 13-7B: Declaring a custom exception in C++

image from book
#ifndef Unit1H #define Unit1H #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> class ENoUpperCaseLetters: public Exception { public:    ENoUpperCaseLetters(const AnsiString Msg): Exception(Msg) {}; }; class TForm1 : public TForm { __published:    // IDE-managed Components    TButton *Button1;    TEdit *Edit1;    void __fastcall Button1Click(TObject *Sender); private:         // User declarations    int __fastcall CountUpperCase(const AnsiString s); public:          // User declarations    __fastcall TForm1(TComponent* Owner); }; extern PACKAGE TForm1 *Form1; #endif
image from book

Listing 13-7C: Working with a custom exception in C++

image from book
#include <vcl.h> #pragma hdrstop #include "Unit1.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner)    : TForm(Owner) { } int __fastcall TForm1::CountUpperCase(const AnsiString s) {    int cnt = 0;    for(int i = 1; i <= s.Length(); i++)    {       if(s[i] >= 'A' && s[i] <= 'Z')         cnt++;    }    if(cnt == 0)       throw ENoUpperCaseLetters("No uppercase letters in the string.");    return cnt; } void __fastcall TForm1::Button1Click(TObject *Sender) {    try    {       int Cnt = CountUpperCase(Edit1->Text);       Caption = IntToStr(Cnt) + " uppercase letter";       if(Cnt > 1) Caption = Caption + "s";    }    catch(ENoUpperCaseLetters &e)    {       Caption = e.Message;    } }
image from book



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net