Erindid
Pärast selle praktikumi läbimist üliõpilane
- teab, mis on erindid ja millal on neid vaja kasutada
- oskab defineerida erindeid ja neid vajadusel visata
- oskab vajadusel erindeid käsitleda
Erindite (exception) töötlemine
C++ keeles on võimalik kasutada erindeid (exceptions) programmi tõrgete ja erandlike olukordade käsitlemiseks. Erindid võimaldavad programmi töö ajal teatud vigu ja tõrkeid tuvastada ja neile reageerida, ilma et programm täielikult katki läheks.
Erindeid saab defineerida kasutades nii standardseid erindeid (nt exception
) kui ka kohandatud erindeid (mis on spetsiaalselt loodud programmi vajadustele). Erindite kasutamine toimub järgmiselt:
1. Erindi defineerimine. Näiteks saab defineerida erindi MinuErind
, millel on string
tüüpi isendimuutuja m_sõnum
, järgmiselt:
class MinuErind{ public : MinuErind(string sõnum) : m_sõnum{sõnum}{} string m_sõnum{}; }; |
2. Erindi viskamine. Erindi viskamiseks kasutatakse võtmesõna throw
ja erindi objekti. Näiteks saab visata erindi MinuErind
järgmiselt:
if (y == 0 ) { throw MinuErind( "Jagamine nulliga!" ); } |
3. Erindi käsitlemine. Erindi käsitlemiseks kasutatakse try-catch
plokki. try
plokis määratakse kood, mis võib tekitada erindeid ja catch
plokis määratakse, kuidas erinditele reageerida. catch
plokki nimetatakse ka püüniseks. Näiteks saab käsitleda erindit MinuErind
järgmiselt:
try { if (y == 0 ){ throw MinuErind( "jagamine nulliga!" ); } } catch (MinuErind& e){ cout << "Viga: " << e.m_sõnum << '\n' ; } |
Paneme nüüd kogu koodi kokku:
|
|
Käsk throw
võib visata ka lihtsalt täisarvu. Oluline on, et leiduks catch
plokk, kus täisarv kinni püütakse. Järgnev näide illustreerib programmi täitmise järjekorda, kus erindite püüdmine on mitmel tasandil. Funktsioon f1()
pöördub funktsiooni f2()
poole, see omakorda f3()
poole, kus visatakse erind ja püütakse ka kinni.
|
|
Funktsioonis f3()
peale erindiviskamist (erindiviskaja()
) läheb juhtimine catch
-plokki ja käsku cout << "f3()-s peale erindiviskamist\n"
ei täideta. Peale seda täidetakse catch
plokile järgnev käsk. Kuna erind on töödeldud, siis funktsioonis f2()
jätkub töö normaalselt, st käsust peale f3()
poole pöördumist, analoogiliselt funktsioonides f1()
ja main()
.
Erindeid ei pea kohe viskamise juures töötlema, vaid selle võib jätta hilisemaks. Muudame funktsiooni f3()
järgmiselt:
void f3() { cout << "f3() alustab" << endl; erindiviskaja(); cout << "f3()-s peale erindiviskamist\n" ; cout << "f3() lõpetab" << endl; } |
Nüüd funktsioonis f3()
erindit ei töödelda, see delegeeritakse pöördujale, st funktsioonile f2()
. Funktsiooni töö tulemuseks on
f1() alustab f2() alustab f3() alustab erindiviskaja() alustab Erindi töötlemine f2()-s f2() lõpetab f1()-s peale f2() täitmist f1() lõpetab main peale f1() täitmist main() lõpetab |
Kui erindi töötlejat ei leita, siis programm lõpetab töö veaga. Kui eemaldada erinditöötlus ka funktsioonidest f2()
, f1()
ja main()
, siis programm lõpetab veateatega
terminate called after throwing an instance of 'int' |
Mõnikord on vaja püünises (catch
-plokis) visata uuesti sama erind. Seda saab teha operaatoriga throw
. Järgmises näites on erindiklassiks klass näita
, kus on konstruktor, koopiakonstruktor ja destruktor; lisaks staatiline väli number
, mille abil loetakse klassist tehtud objekte (ka koopiaid). Funktsioon loenda
on rekursiivne ja erind visatakse parameetri n = 1
korral. Püünises visatakse uuesti sama erind operaatoriga throw
.
|
|
Seda erindit asutakse püüdma funktsiooni väljakutsete magasinis, kus püünises visatakse iga kord erind uuesti. Funktsioonis loenda
on püünises erindi edastamine viite abil catch (näita& ex)
, aga funktsioonis main
koopia abil catch (näita ex)
, st toimub koopiakonstruktori poole pöördumine ja koopia hiljem hävitatakse. Esialgselt visatud erind kustutatakse alles main
funktsioonis.
Kui funktsioonis loenda
kasutada erindi edastamist koopia abil catch (näita ex)
, siis tehakse edastamiseks erindist iga kord koopia ja püünises see koopia hävitatakse:
algab loenda( 3 ) algab loenda( 2 ) algab loenda( 1 ) näita konstruktor ( exception : 1 ) näita koopiakonstruktor ( exception : 2 ) catch n = 1 ~ näita destruktor ( exception : 2 ) näita koopiakonstruktor ( exception : 3 ) catch n = 2 ~ näita destruktor ( exception : 3 ) näita koopiakonstruktor ( exception : 4 ) catch n = 3 ~ näita destruktor ( exception : 4 ) näita koopiakonstruktor ( exception : 5 ) catch main ( exception : 5 ) ~ näita destruktor ( exception : 5 ) ~ näita destruktor ( exception : 1 ) Kõik valmis! |
Seega mõistlik on võimalusel kasutada erindi edastamiseks viidet.
Teegis <exception>
on C++ erindite klassid, mida saab erindite töötlemisel kasutada. Toome erindite klassi hierarhiast olulisema osa:
![]() |
Oma erindiklassi saab pärida ka sisseehitatud klassist. Järgmises näites visatakse erind peale negatiivse arvu sisestamist. Programmi jooksutati kaks korda.
|
|
noexcept
Mõned funktsioonid ei tohiks kunagi erindit tekitada. Näiteks allolevas klassis A
funktsioon getA
, mis tagastab täisarvu. Sellisele funktsioonile võib lisada täiendi noexcept
. Kui kompilaator teab, et funktsioon ei viska kunagi erindit, loob ta efektiivsema objektikoodi.
class A{ int m_a{}; public : A( int a) : m_a{a}{} int getA() noexcept{ return m_a; } }; |
Täpsemalt saab erindite kasutamist uurida aadressil