-
C++ Builder podporuje zpracování výjimek C++, zpracování strukturovaných
výjimek C a zpracování výjimek VCL. Příklady uvedené v této kapitole pro
C++ a C výjimky mohou být přeloženy jako konzolové aplikace.
Výjimky popisují výjimečné situace, které vyžadují speciální zpracování
a mohou popisovat chyby vznikající za běhu aplikace (např. dělení nulou
nebo nedostatek volného místa v paměti). Zpracování výjimek nabízí způsob
řešení chyb. Obsluhu výjimky můžeme použít pro specifikaci akce, která
bude provedena po vzniku chyby před ukončením programu. Jsou zpracovávány
pouze synchronní výjimky, tzn. výjimky, které jsou generovány naší aplikací.
Jazyk C++ určuje, že všechny výjimky vznikají v bloku try (chráněný
blok). Tento blok je následován jedním nebo více bloky catch, které
identifikují a zpracují výjimky generované v bloku try.
Zpracování výjimek vyžaduje použití tří klíčových slov: try,
catch
a throw. Když program C++ způsobí výjimku, pak řízení je předáno
na jinou část programu (obsluhu výjimky), která zpracovává tento typ výjimky.
Obsluha výjimku zachycuje.
Program výjimku generuje provedením příkazu throw. Příkaz throw
se obecně vyskytuje uvnitř funkce a má tvar:
throw "overflow";
Tento příkaz vytvoří objekt, který popisuje typ výjimky (v našem případě
aritmetické přetečení). Jiná část programu může zachytit vygenerovaný objekt
výjimky a zpracovat jej. K použití zpracování výjimek, musíme obklopit
náš kód konstrukcí try / catch. Syntaxe této konstrukce je:
blok-try:
try složený-příkaz seznam-obsluh
seznam-obsluh:
obsluha [seznam-obsluh]
obsluha:
catch (deklarace-výjimky)
složený-příkaz
deklarace-výjimky:
seznam-specifikací-typů deklarátor
seznam-specifikací-typů abstraktní-deklarátor
seznam-specifikací-typů
příkaz-throw:
throw [přiřazovaní-výraz]
Klíčová slova try, catch a throw nejsou v jazyku
C povolena. Blok try musí být následován blokem catch. Příkazy
v bloku try jsou prováděny běžným způsobem. Pokud v bloku try
je generována výjimka, pak řízení provádění programu je předáno na příslušnou
obsluhu výjimky. Obsluha je blok kódu určený k zpracování výjimky. V jazyku
C++ musí být za blokem try alespoň jedna obsluha. Program musí obsahovat
obsluhu pro každou výjimku, která může být programem generována.
Přestože výjimky mohou být libovolného typu, je vhodné vytvářet objekty
výjimek. Objekty výjimek jsou brány jako libovolné jiné objekty. Výjimka
nese informace z místa vzniku výjimky do místa jejího zachycení. Jedná
se o informaci, kterou uživatel aplikace potřebuje znát při výskytu chyby
za běhu aplikace. C++ má řadu předdefinovaných výjimek.
Blok kódu, ve kterém výjimka může vzniknout, musí předcházet klíčové
slovo try. Jestliže výjimka nastane, pak běh programu je přerušen
a program hledá příslušnou obsluhu výjimky. Pokud obsluha je nalezena,
pak řízení je na ní předáno, není-li nalezena, pak voláním set_terminate
mohla být nastavena funkce, která bude provedena; jinak program volá funkci
terminate.
Pokud žádná výjimka nenastane, pak program je proveden normálně.
Následuje řada příkladů, ve kterých jsou použity různé způsoby generování
výjimek. Následující příklad předává objekt Out obsluze (je potřeba
jej přeložit jako konzolovou aplikaci):
#include <conio.h>
#include <iostream.h>
bool pass;
class Out{};
void festival(bool
firsttime){ if(firsttime) throw Out();
}
int main()
{
try
{ pass = true;
festival(true);
}
catch(Out&
e){ pass = false; }
cout <<
(pass ? "true" : "false") << endl;
getch();
return 0;
}
Zde v bloku try je volána funkce festival, která generuje
objekt Out. Tento objekt je zachycen obsluhou (je zde změněna hodnota
proměnné pass na false). Následující příklad generuje zachycenou
výjimku znova (příkaz throw bez parametru). Výjimka již musí existovat.
#include <conio.h>
#include <iostream.h>
bool pass;
class Out{};
void festival(bool
firsttime){ if(firsttime) throw Out(); }
void test()
{
try { festival(true);
}
catch(Out&
e){ pass = false; throw;
}
}
int main()
{
try
{ test();
}
catch(Out&
e){ pass = true; }
cout <<
(pass ? "true" : "false") << endl;
getch();
return 0;
}
Funkce test volá funkci festival, ve které je generována
výjimka
Out. Výjimka je zachycena obsluhou v test a opětovně
generována (předána ven z funkce). Další příklad volá námi nastavenou ukončující
funkci (my_terminate) při pokusu o opětovné generování výjimky,
která nenastala.
#include <conio.h>
#include <iostream.h>
bool pass;
class Out{};
void my_terminate(){
cout <<
"Žádná výjimka" << endl;
getch();
exit(1);
}
void festival(bool
firsttime){ if(firsttime) throw Out(); }
void test()
{
try { festival(false);
}
catch(Out&
e){ pass = false; }
throw;
// nelze opětovně generovat výjimku, která nenastala
}
int main()
{
set_terminate(my_terminate);
try {
test();
}
catch(Out&
e){ pass = true; }
cout <<
(pass ? "true" : "false") << endl;
getch();
return 0;
}
-
Další příklad specifikuje seznam výjimek, které mohou být generovány jednotlivými
funkcemi (festival a test). Ve funkci festival může
vznikat výjimka Out a ve funkci test žádná výjimka.
#include <conio.h>
#include <iostream.h>
bool pass;
class Out{};
void festival(bool
firsttime) throw(Out) // pouze výjimka Out
{
if(firsttime)
throw Out();
}
void test() throw()
// žádná výjimka
{
try {
festival(true);
}
catch(Out&
e){ pass = true; }
}
int main()
{
pass = false;
test();
cout<<(pass?"test
zpracoval výjimku":"výjimka nezpracována")<<endl;
getch();
return 0;
}
Pokud festival generuje jinou výjimku než Out, pak je
považována za neočekávanou výjimku a řízení programu je předáno na funkci
unexpected,
jak je ukázáno v následujícím příkladě.
Následující příklad ukazuje test, která nemá generovat žádnou
výjimku. Pokud nějaká funkce (např. operátor new) v těle test
generuje výjimku, pak výjimka musí být zpracována uvnitř test. Jinak
výjimka porušuje seznam specifikací výjimek pro test. Funkcí set_unexpected
můžeme nastavit jinou funkci pro neočekávanou výjimku; jinak, je volána
funkce unexpected.
#include <conio.h>
#include <iostream.h>
bool pass;
class Out{};
void my_unexpected(){
cout <<
"Chyba test" << endl;
getch();
exit(1);
}
void festival(bool
firsttime) throw(Out) // pouze výjimka Out
{
if(firsttime)
throw Out();
}
void test() throw()
// žádná výjimka
{
try { festival(true);
}
catch(Out&
e){ pass = true; throw; } // opětovné generování
Out - chyba
}
int main()
{
set_unexpected(my_unexpected);
pass = false;
test();
cout<<(pass?"test
zpracoval výjimku":"výjimka nezpracována")<<endl;
getch();
return 0;
}
Když výjimka nastane, pak příkaz-throw inicializuje dočasný
objekt typu T (typ parametru par) použitý v throw(T par).
Další kopie mohou být generovány podle požadavků překladače. Je tedy užitečné
definovat pro objekt výjimky kopírovací konstruktor, jak je ukázáno v následujícím
příkladě.
#include <conio.h>
#include <iostream.h>
class festival
{
public:
festival()
{ cout << "vytvoření festival" << endl; }
festival(const
festival&){cout<<"vytvoření kopie festival" << endl;}
~festival()
{ cout << "zrušení festival" << endl; }
};
int main()
{
try {
cout << "generování výjimky festival" << endl;
throw(festival());
}
catch(festival&){
cout << "vytvoření festival" << endl;
}
getch();
return 0;
}
-
Obsluha výjimky je specifikována klíčovým slovem catch, které je
umístěno bezprostředně za blok try. Může se také vyskytnout bezprostředně
za jiným blokem catch. Každá výjimka generovaná programem musí být
zachycena a zpracována obsluhou výjimky. Obsluha zachycuje výjimku, když
se její typ shoduje (nebo může být převedena) s typem v příkazu catch.
Při shodě je řízení předáno obsluze. Obsluha určuje, které akce mají být
provedeny. Po dokončení obsluhy, program pokračuje za poslední obsluhou
pro současný blok try. Žádná další obsluha pro současnou výjimku
není vyhodnocena. K přenosu řízení mimo obsluhu může být použit příkaz
goto.
Pokud vznikne chyba při provádění obsluhy výjimky, pak program je ukončen.
Obsluhu výjimek si ukážeme na následujícím příkladě.
#include <conio.h>
#include <iostream.h>
class festival{};
class Harvest : public
festival{};
class Spring : public
festival{};
void ToHaveFun(int
i)
{
if(i==1) throw(Harvest());
else
throw(Spring());
}
int main()
{
try {
ToHaveFun(0);
}
catch(const
Harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
Spring&) {cout << "generováno Spring Festival" << endl;
}
try {
ToHaveFun(1);
}
catch(const
Harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
Spring&) {cout << "generováno Spring Festival" << endl;
}
getch();
return 0;
}
Další příklad ukazuje, že když zachytíme výjimku a tato výjimka je
součástí hierarchie tříd, pak je nutno začít zachytáváním nejvíce odvozené
třídy výjimky.
#include <conio.h>
#include <iostream.h>
class festival{};
class harvest
: public festival{};
class spring : public
festival{};
void ToHaveFun(int
i)
{
if (i==1)
throw(harvest());
else if(i==2)
throw(spring());
else throw(festival());
}
int main()
{
/* Při zachycování
výjimek záleží na pořadí */
try { ToHaveFun(0);
}
catch(const
harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
spring&) {cout << "generováno Spring Festival" << endl;
}
catch(const
festival& ){cout << "generováno Festival" << endl; }
try { ToHaveFun(1);
}
catch(const
harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
spring&) {cout << "generováno Spring Festival" << endl;
}
catch(const
festival& ){cout << "generováno Festival" << endl; }
try { ToHaveFun(2);
}
catch(const
harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
spring&) {cout << "generováno Spring Festival" << endl;
}
catch(const
festival& ){cout << "generováno Festival" << endl; }
/* Zachytávání
základní třídy dříve než odvozených tříd způsobí, že
odvozená výjimka je zachycena obsluhou základní třídy výjimky */
try { ToHaveFun(1);
}
catch(const
festival&){cout<<"generováno Festival (je správné?)"<<endl;}
catch(const
harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
spring&) {cout << "generováno Spring Festival" << endl;
}
try { ToHaveFun(2);
}
catch(const
festival&){cout <<"generováno Festival (je správné?)"<<endl;}
catch(const
harvest&){cout << "generováno Harvest Festival" << endl;
}
catch(const
spring&) {cout << "generováno Spring Festival" << endl;
}
getch();
return 0;
}
V následujícím příkladu příkaz catch (...) zachytí výjimku libovolného
typu. Tento příkaz je jediná obsluha výjimky bloku try.
#include <conio.h>
#include <iostream.h>
bool pass;
class Out{};
void festival(bool
firsttime) throw(Out)
{
if(firsttime)
throw Out();
}
void test() throw()
{
try { festival(true);
}
catch(...){
pass = true; }
}
int main()
{
pass = false;
test();
cout<<(pass?"test
zpracoval výjimku":"výjimka nezpracována")<<endl;
getch();
return 0;
}
-
C++ poskytuje službu nazvanou specifikace výjimek, která umožní specifikovat
seznam výjimek, které funkce může generovat. Tato specifikace se zapisuje
jako přípona deklarace funkce. Použití přípony specifikací výjimek neovlivňuje
typ funkce. V předchozích příkladech specifikace výjimek již byla použita.
Následují ukázky deklarací funkcí se specifikacemi výjimek:
void f1();
// Funkce generuje libovolné výjimky
void f2() throw();
// Funkce negeneruje žádnou výjimku
void f3() throw(
A, B* ); // Funkce může generovat výjimky odvozené od A
// nebo od ukazatele na B
Pokud funkce generuje výjimku neuvedenou ve specifikaci, pak program
volá unexpected.
-
Win32 podporuje zpracování strukturovaných výjimek C, které se podobají
výjimkám C++. Jsou zde jisté odchylky, což vyžaduje opatrnost při míchání
těchto dvou typů výjimek. Při používání strukturovaných výjimek je nutno
v aplikacích C++ Builderu dodržovat tato pravidla:
-
Strukturované výjimky mohou být použity v C++ programech.
-
C++ výjimky nemohou být používány v C programech (klíčové slovo catch
není přípustné v C).
-
Výjimky generované voláním funkce RaiseException jsou zpracovány
blokem try / __except (C++) nebo __try / __except (C). Je
možno použít i bloky try /__finally nebo __try / __finally.
Při volání RaiseException jsou ignorována zpracování bloky try
/ catch.
-
Výjimky, které nejsou zpracovány obsluhou, nevolají terminate, ale
jsou předány operačnímu systému (konečným výsledkem je ukončení procesu).
-
Zpracování výjimek nevyžaduje kopii objektu výjimky, pokud ji nevyžadujeme
sami.
V C programech používáme k implementaci strukturovaných výjimek klíčová
slova __except, __finally a __try. Klíčové slovo __try
lze použít pouze v C programech. Pokud chceme zapsat přenositelný kód,
pak strukturované výjimky není vhodné používat.
Strukturované výjimky mohou být zpracovány pomocí rozšíření zpracování
C++ výjimek:
try {
funkce();
}
__except(__expr__)
{
// obsluha
}
__expr__ je výraz, jehož vyhodnocení je jedna ze třech hodnot:
EXCEPTION_CONTINUE_SEARCH
(0 - obsluha není zahájena a OS pokračuje v hledání obsluhy výjimky), EXCEPTION_CONTINUE_EXECUTION
(-1 - provádění pokračuje v bodě výjimky) a EXCEPTION_EXECUTE_HANDLER(1
- obsluha výjimky je provedena).
Win32 poskytuje dvě funkce, které mohou být použity k zjištění informací
o aktivní výjimce: GetExceptionCode a GetExceptionInformation.
Jestliže chceme tyto funkce volat jako část výrazu filtru, pak je musíme
volat přímo v __except. Výraz filtru slouží k filtrování výjimek.
-
Pokud mícháme C++ a strukturované výjimky, pak nesmíme zapomenout na některé
věci. I když C++ Builder implementuje C++ výjimky se strukturovanými výjimkami
Win32, pak v bloku __except jsou výjimky C++ transparentní. Blok
try
může být následován jedním blokem __except nebo alespoň jedním blokem
catch.
Tyto bloky nelze míchat, ale je možno vnořit dva bloky try do sebe:
try {
EXCEPTION_POINTERS
*xp;
try {
funkce();
}
__except(xfilter(xp
= GetExceptionInformation())) {
//...
}
}
catch (...) {
//...
}
Míchání výjimek si ukážeme na následujícím příkladě. Přeložte jej jako
konzolovou aplikaci a pokuste se pochopit, kdy výjimky vznikají a jak jsou
zpracovány.
#include <iostream.h>
#include <conio.h>
class Exception
{
public:
Exception(char*
s = "Unknown"){ what = strdup(s); }
Exception(const
Exception& e ){ what = strdup(e.what); }
~Exception()
{ delete[] what; }
char* msg()
const
{ return what;
}
private:
char* what;
};
int main()
{
float e, f,
g;
try {
try {
f = 1.0;
g = 0.0;
try {
cout << "výjimka: " << endl;
e = f / g;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
cout << "Zachycení strukturované výjimky. " << endl;
throw(Exception("Chyba: Dělení 0"));
}
}
catch(const Exception& e) {
cout << "Zachycení C++ výjimky: " << e.msg() << endl;
}
}
__finally
{
cout << "C++ umožňuje také použít __finaly " << endl;
}
getch();
return e;
}
-
Jak již jsme viděli v předchozím příkladě, model zpracování strukturovaných
výjimek podporuje ukončující blok (__finally). Tento blok je prováděn
vždy (když výjimka vznikne i když nevznikne).
try {
funkce();
}
__finally {
// zde uvedené
příkazy jsou provedeny vždy
}
Použití ukončovacího bloku je uvedeno v následujícím příkladě.
#include <conio.h>
#include <iostream.h>
int main()
{
float e, f,
g;
try {
f = 1.0;
g = 0.0;
try {
cout << "Výjimka: " << endl;
e = f / g;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
cout << "Zachycení výjimky. " << endl;
}
}
__finally
{
cout << "Je provedeno také. " << endl;
}
try {
f = 1.0;
g = 2.0;
try {
cout << "Není výjimka: " << endl;
e = f / g;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
cout << "Zachycení výjimky. " << endl;
}
}
__finally
{
cout << "Je provedeno také. " << endl;
}
getch();
return e;
}