Általában véve a kivétel olyan hibás állapot vagy esemény, amely megszakítja az alkalmazás szabályszerű futás folyamatát. Amikor fellép egy kivétel, a vezérlés a futóprogram aktuális pontjáról a kivételkezelőhöz (exception handler) adódik át. Az Object Pascal a kivételkezelés támogatására olyan strukturális eszközöket tartalmaz, amelyek lehetővé teszi a normális programlogika és hibakezelési logika kettéválasztását. Ezzel nagyban növekszik az alkalmazások karbantarthatósága.
Az Object Pascal a kivételek megvalósítására objektumokat használ, melynek a legfontosabb előnyei így foglalhatóak össze:
A kivételkezelés használatához az alkalmazásnak szüksége van a SysUtils unitra, amely az Object Pascal futási idejű könyvtárához (run-time library) definiálja kivételeket. Amikor egy alkalmazás használja a SysUtils unitot, a futási idejű hibák automatikusan kivételekké alakulnak. Ennek köszönhetően az olyan hibaállapotokat – mint például a memória túlcsordulása, nullával való osztás és az általános védelmi hiba -, amelynek máskülönben az alkalmazás leállításához vezetne, el lehet fogni és strukturált módon fel lehet dolgozni (kezelni).
Megjegyzés: a Delphi Visual Component Library teljes egészében támogatja a kivételkezelést. A VCL–en alapuló alkalmazások automatikusan használják a SysUtils unitot, engedélyezve ezzel a kivételkezelést. A TForm osztály az üzenet-feldolgozó ciklusában kezeli a kivételeket, így egy eseménykezelőben fellépő kezeletlen kivételtől nem áll le a program, csak egy hibaüzenet jelenik meg.
Object Pascalban a kivétel egyszerűen egy osztályt jelent, így a kivétel deklarációja semmivel sem különbözik egy közönséges osztálydeklarációtól. Kivétel objektumnak tetszőleges osztály példányát lehet használni, azonban ajánlatos minden kivételt a SysUtils unitban definiált Exception osztályból származtatni:
Az egymással összefüggő kivételeket - az öröklődés adta lehetőségeket kihasználva – családokba szokás csoportosítani. A példában szereplő deklarációk a matematikai kivételek családját definiálják. A kivételcsaládok révén lehetőség nyílik arra, hogy a kivételek csoportját egyetlen névvel azonosítjuk. Például, az EMathError kivételt feldolgozó kód kezelni fogja az összes EMathError osztályból közvetlenül vagy közvetett módon származtatott kivételt.
Vannak esetek, amikor a származtatott kivételosztályok újabb adatmezőket, metódusokat és jellemzőket vezetnek be a kivételekhez kapcsolódó kiegészítő információk továbbítására. Például, a SysUtils unitokban definiált EInOutError osztály az ErrorCode mezőt vezeti be, amely a kivételt kiváltó file-hiba kódját tartalmazza:
Kivételeket a raise utasítás segítségével válthatunk ki:
A raise kulcsszó után osztálytípus objektumnak kell állnia. Amikor fellép egy kivétel, a kivételkezelő logika átveszi a kivétel objektum birtoklási jogát. Miután a kivételkezelés megtörtént, a kivétel-objektum automatikusan törlődik a Destroy destruktor hívása révén. Fontos, hogy a raise utasítás objektumot, nem pedig osztályt vár az argumentumában. Általában a kivétel-objektumot közvetlenül az argumentumban hozzuk létre a megfelelő kivételosztály Create konstruktorának meghívásával. Az argumentum nélküli raise utasítás újra kiváltja az aktuális kivételt.
A raise utasítás nem „tér vissza” a szokásos értelemben. Ehelyett a raise a vezérlést annak a legbelső kivételkezelőnek adja, amely alkalmas az adott osztályú kivételek kezelésére. A legbelső ebben az esetben azt a kezelőt jelenti, amelynek a try ... except blokkjába legkésőbb léptünk be, és még nem léptünk ki belőle.
Példa: a következő Str2IntR függvény stringet egésszé alakít. Ha az eredmény kilép a megadott intervallumból, egy ERangeError kivétel jön létre:
Fontos: egy unit initialization részében eldobott kivétel nem a várt hatást fogja kiváltani, mert a kivételkezelés a SysUtils unitban van bevezetve, és az ilyenkor még nem aktív.
A kivételek kezelése a try ... except utasítás segítségével történik. Az utasítás alakja a következő:
Az except blokk kivételkezelőit a következő alakban adhatjuk meg:
A try ... except utasítás egymás után végrehajtja az utasítások listájában szereplő utasításokat. Ha a végrehajtás során semmilyen hiba nem történt, akkor a kivétel blokk figyelmen kívül marad, és a vezérlés a try ... except utasítást záró end kulcsszót követő utasításra adódik.
Az except ... end rész kivételkezelőket tartalmaz. Valamely kivételkezelő meghívását csak a try ... except részben, illetve a try ... except részből hívott eljárásban vagy függvényben fellépő hiba, vagy raise utasítás végrehajtása eredményezi.
Amikor fellép egy kivétel, a vezérlés az ahhoz a legbelső kivételkezelőhöz kerül, amely alkalmas az adott osztályú kivételek kezelésére. A megfelelő kivételkezelő keresése az utolsóként végrehajtott try ... except utasításokban kezdődik. Ha ez nem tartalmaz megfelelő kivételkezelőt, akkor a keresés az egymásba ágyazott try ... except utasításokon keresztül kifelé haladva folytatódik. Ha egyetlen aktív try ... except utasítás sem tartalmazza a szükséges kezelőt, akkor a program futása hibaüzenettel megszakad.
A megfelelő kivételkezelő keresése a kivétel blokk on ... do kezelőinek vizsgálatával történik. Az on ... do kivételkezelőket a beírás sorrendjében ellenőrzi a rendszer, kiválasztva az első olyan kezelőt, amely típuskompatibilis az aktuális kivétellel. Ezért ajánlott, hogy mindig a származtatott kivételek kezelőit adjuk meg először a kezelőlistában.
Példa: A korábban említett kivételeket a megfelelő sorrendben adtuk meg:
Ha a kivétel blokk tartalmaz else részt is, akkor az except részben folytatott sikertelen keresés esetén az else rész fog találtnak számítani.
Példa: az else ágban minden nem EMathError kivétel kezelése megtörténik:
Ha a kivétel blokk csak utasításokat tartalmaz, akkor ez találtnak számít minden kivétel számára:
Az on ... do kivételkezelőben nem kötelező azonosító: paramétert az on ... do utasítás végrehajtás során a kivételobjektum azonosítására használhatjuk:
Olyan esetekben, mikor egy eljárás vagy egy függvény nem kezeli a benne fellépő kivételt, a kivételt tovább kell adni a függvényt hívó külső try ... except utasításnak, amelynek az except része kezelőket tartalmaz az adott jellegű kivételek feldolgozására. Ezt a mechanizmust az eredeti kivétel újraaktivizálásának nevezzük.
A kivételt ismételt előidézése a raise utasításnak a try ... except utasítás except részében történő megadásával végezhető el:
Ha a kivételt újraaktivizáló függvény bármilyen memóriafoglalással kapcsolatos műveletet tartalmaz, akkor a kivétel ismételt előidézése előtt feltétlenül fel kell szabadítanunk a lefoglalt memóriaterületet vagy objektumokat.
Példa:
A kivételkezelőben végrehajtódó kód maga is kiválthat és kezelhet kivételeket. Ha a kivételkezelőben kiváltott kivételeket a kezelőn belül dolgozzuk fel, akkor ezek semmi hatással sincsenek az eredeti kivételre. Ha azonban a kivételkezelőben fellépő kivétel átterjed a kezelő határain kívülre, akkor az eredeti kivétel elveszik.
Példa:
A tangens függvényben az EMathError kivétel létrejötte esetén a kivételkezelő egy ETanError kivételt hoz létre. Mivel ennek a kivételnek a kezelését nem tartalmazza a Tan függvény, ezért ez a kivétel az eredeti kivételkezelőn kívülre kerül és a függvényt hívó számára az eredmény egyszerűen az lesz, hogy a Tan függvény egy ETanError kivételt váltott ki.
Amikor egy kódrészlet valamilyen erőforrást használ, szükséges lehet annak biztosítása, hogy mind a normális, mind pedig kivétellel megszakított programvégrehajtás esetében felszabadítsuk a lefoglalt erőforrást. Például, a file-nyitást és feldolgozást követően mindig biztosítani kell a file lezárását. Ilyen esetekben a try ... finally utasítást lehet használni:
A try ... finally utasítás egymás után végrehajtja a try és finally között álló utasításokat. Abban az esetben, ha a végrehajtás alatt nem történik semmilyen kivételt kiváltó hibás állapot, akkor egyszerűen végrehajtódnak a finally és az end között álló utasítások is. Ha azonban az első rész végrehajtása alatt bármelyik utasítás hibát váltott ki, a vezérlés átmegy a finally részhez, és az ott megadott utasítások végrehajtása után a kivétel automatikusan újra fellép, melynek feldolgozása általában a külső try ... except utasításban történik. Ezzel a megoldással a finally rész utasításai mindig végrehajtódnak, függetlenül a try rész feldolgozásának sikerességétől.
Példa: a try ... finally biztosítja, hogy a nyitott állomány mindig bezárjuk:
A try ... finally alakú utasításokat általában gyakrabban használja egy alkalmazás, mint a try ... except alakúakat. Mivel a Delphi Visual Component Libraryt használó alkalmazások az esetek többségében a VCL alapértelmezés szerinti kivételkezelő mechanizmusait használják, ezért ritkán van szükség a try…except utasításokra, és inkább a try ... finally utasításokat alkalmazzák.
Olyan esetekben azonban, mint például a memóriafoglalás, szükség van a kivételek elleni védekezésre, így az ilyen alkalmazásokban gyakoribb a try ... except utasítások használata, amelyek természetesen tartalmazhatnak beágyazott try ... finally utasításokat is.
Megjegyzés: A beágyazásnál azonban vigyázni kell arra, hogy a try ... finally utasítás finally részében fellépő lokális kivételeket ugyanitt kezeljük, máskülönben az eredetileg kiváltott kivétel helyett ez a lokális kivétel kerül ki a külső kivételkezelőhöz, az eredeti pedig elvész.
A try ... finally utasítás try részéből bármikor átadhatjuk a vezérlést a finally résznek a szabályos Exit, Break vagy Continue eljárások hívásával. Ezen függvények használhatók a kivételkezelőből való kilépésre is, ekkor a kivétel objektum automatikusan megszűnik.
Az előre definiált kivételosztályokat a SysUtils modulban találhatjuk az Exception osztállyal együtt, amely minden más kivételosztály alaposztálya. Az Exception osztály két tulajdonságát (property), a Message-et és a HelpContextet minden kivételosztály örökli. Ezért minden kivételobjektum rendelkezhet hibaállapotot leíró üzenettel és a megfelelő súgófejezetre hivatkozó azonosítóval. Az Exception osztálynak több konstruktora is van, amelyek különböző lehetőségeket kínálnak a Message és a HelpContext inicializálására. Ezek a konstruktorok:
Create, CreateFmt, CreateRes, CreateResFmt, CreateHelp, CreateFmtHelp, CreateResHelp, CreateResFmtHelp.
A következő táblázatban szerepelnek a SysUtils modulban definiált kivételek, és az őket kiváltó állapotok:
EAbort |
Abort() utasítás által kiváltott kivétel. |
EAbstractError |
Absztrakt metódus hívása által kiváltott kivétel. |
EAccessViolation |
Érvénytelen memóriacímre való hivatkozás által kiváltott kivétel. |
EAssertionFailed |
Assert eljárás false paraméterrel való hívása által kiváltott kivétel, ha a $ASSERTIONS ON érvényben van. |
EControlC |
Konzol alkalmazás futása közben ütött Ctrl+C által kiváltott kivétel. |
EConvertError |
Konverziós függvény végrehajtása során fellépő hiba által kiváltott kivétel. |
EDivByZero |
Egész számok nullával való osztása által kiváltott kivétel. |
EExternal |
Az összes Win32 rendszerre épülő kivétel ősosztálya. |
EExternalException |
Az EExternal leszármazottja, akkor lép fel, ha egy másik kivétel kivételkódja érvénytelen. |
EHeapException |
A heap memória használata során fellépő hibák által kiváltott kivétel. |
EInOutError |
Háttértáron lévő állományok I/O műveleteinek hibái által kiváltott kivétel. |
EIntError |
Aritmetikai műveletek kivételeinek őse. |
EIntfCastError |
Hibás interface-konverzió által kiváltott kivétel. |
EIntOverflow |
Egész számok túlcsordulása által kiváltott kivétel. Csak ha $Q+ érvényben van. |
EInvalidCast |
Az as operátor jobb és bal oldalán álló objektumok típusainak meg nem egyezése által kiváltott kivétel. |
EInvalidOp |
Érvénytelen matematikai művelet (pl. négyzetgyök negatív számból) által kiváltott kivétel. |
EInvalidOpCode |
CPU-nak küldött érvénytelen utasítás által kiváltott kivétel. |
EInvalidPointer |
Érvénytelen mutató által mutatott memóriaterület felszabadítása által kiváltott kivétel. |
EMathError |
Lebegőpontos matematikai műveletek kivételeinek ősosztálya. |
EOutOfMemory |
Egy adott művelethez nem elegendő szabad memóriaterület esetén kiváltott kivétel. |
EOverflow |
Lebegőpontos művelet túlcsordulása által kiváltott kivétel. |
EPackageError |
Csomagokkal kapcsolatos hibák által kiváltott kivétel. |
EPrivilege |
Processzor védelmi rendszerének megsértése által kiváltott kivétel. |
ERangeError |
Intervallumellenőrzési hiba által kiváltott kivétel. Csak $R+ érvényessége esetén. |
EStackOverflow |
Stack elfogyása által kiváltott kivétel. |
EUnderflow |
Lebegőpontos műveletek alulcsordulása által kiváltott kivétel. |
EVariantError |
Variáns típus hibái által kiváltott kivétel. |
EWin32Error |
Windows hibák által kiváltott kivétel. |
EZeroDivide |
Nullával való lebegőpontos osztás által kiváltott kivétel. |
A SysUtils modulban a következő, kivételkezelést támogató eljárások és függvények találhatók: