Facultatea de Automatic i Calculatoare PROGRAMARE ORIENTAT OBIECT Curs 12 Introducere Fie un program compus din mai multe pri/module dezvoltate separat. O parte a programului este apelat pentru a executa un anumit task. Respectiva parte poate fi vzut ca o colecie de funcii, o librrie. O librrie este un cod obinuit dar n contextul gestiunii erorilor proiectantul/dezvoltatorul (autorul) acelei librarii nu cunoate din ce fel de programe va face parte librria respectiv. Astfel: Autorul acelei librrii poate detecta erorile ce apar la rularea programului (run time) dar, n general, nu tie ce decizie s ia. Utilizatorul unei librrii poate gestiona o eroare de run time dar nu o poate detecta uor Modul traditional de gestiune al erorilor 1. Terminarea programului (abordare drastic). Fie funcia:
Totui pentru majoritatea erorilor se poate face mai mult: Afiarea/logarea unui mesaj de eroare inainte de a nchide programul.
void *xmalloc(size_t size) { void *pv = malloc(size); if(!pv) { exit(-1); } else { return pv; } } cout << "Eroare alocare memorie"<<endl; Modul traditional de gestiune al erorilor n particular, o librrie nu tie care este scopul i strategia programului n care este ncapsulat. Astfel, n cazul unei erori, nu trebuie doar s apeleze exit() sau abort(). O librrie care nchide programul ntr-un mod necondiionat nu poate fi utilizat ntr-un program care nu trebuie s crape.
Modul traditional de gestiune al erorilor 2. Returnarea unui cod de eroare. Nu este ntotdeauna convenabil deoarece unele funcii sunt apelate n expresii a cror valoare urmeaz a fi determinat. Funcia poate fi modificat pentru a returna o pereche de valori (o structur cu rezultatul funciei si un cod de eroare ce caracterizeaz rezultatul). Dar acest lucru nu convine deoarece pentru fiecare apel trebuie verificat codul de eroare. De asemenea, programatorii ignor sau uit s testeze codul de eroare returnat. Exemplu, funcia printf() returneaz o valoare negativ dac apare o eroare la scrierea n stream. Acel cod de eroare niciodat nu este testat. Constructorii nu returneaz nimic.
double x = sqrt(15); Modul traditional de gestiune al erorilor 3. Returneaz o valoare ns las programul ntr-o stare de eroare Funcia apelat nu notific n nici un fel programul despre starea de eroare. Foarte multe funcii din librria standard C seteaz variabila global errno pentru a indica o eroare.
Valoarea variabilei x nu are sens avnd n vedere c variabila errno a fost setat pentru a indica un argument inacceptabil pentru funcia sqrt. Programele scrise nu testeaz variabile errno. Avnd n vedere existena variabilei globale errno pot s apar probleme n prezena concurenei. double x = sqrt(-1.0); Modul traditional de gestiune al erorilor 4. Apelul unei funcii error-handler:
Intrebarea care apare: Ce face funcia ErrorHandlerFunction(). Dac funcia nu rezolv complet problema atunci aceasta trebuie s: Termine programul Returneze un cod de eroare Seteaz o stare de eroare Arunc o excepie Dac funcia gestioneaz eroarea cu succes atunci de ce este considerat eroare? n mod tradiional astfel de abordri co-exist n programe
if(error) { ErrorHandlerFunction(); } Excepii Noiunea de excepie este furnizat pentru a obine informaii din punctul unde este detectat o eroare n punctul n care poate fi gestionat. O funcie ce nu poate gestiona o problem, arunc o excepie (throws) spernd ca apelantul (direct ori indirect) s gestioneze problema. O funcie care vrea s gestioneze o anumit problema indic acest lucru prinznd excepia respectiv: O component de apel indic tipurile de erori pe care le poate gestiona specificnd excepiile respective n clauza catch a unui bloc try. Componenta apelat care nu ii poate duce taskul la bun sfrit raporteaz eroarea aruncnd o excepie Excepii void Taskmaster() { try { auto result = DoTask(); // ok } catch (someError) { // eroare in indeplinirea taskului. Gestioneaza. } } int DoTask() { // ... if (/*daca nu au aparut erori*/) return result; else throw someError{}; } Funcia TaskMaster ntreab DoTask() n legtur cu o sarcin de indeplinit. Dac funcia DoTask() i-a ndeplinit sarcina i ntoarce un rezultat corect, totul este bine. Altfel, trebuie s raporteze erori aruncnd excepii. TaskMaster() este pregtit s gestioneze eroarea aprut dar totodat este posibil ca funia DoTask() s apeleze la rndul ei alte funcii care la randul lor pot arunca excepii. O altfel de eroare dect cea pentru care TaskMaster() este pregtit indic un eec al funciei n indeplinirea sarcinei i trebuie gestionat de secvena de cod ce a apelat TaskMaster(). O funcie apelat nu trebuie doar s returneze o eroare a aprut. Pentru ca programul s fie funcional, o funcie apelat trebuie s lase programul ntr-o stare bun fr scurgeri de resurse. Excepii Mecanismul de gestiune a excepiilor: Este o alternativ la tehnicile tradiionale atunci cnd acestea sunt insuficiente, neelegante ori generatoare de erori. Este complet; poate fi folosit la gestiunea tuturor erorilor detectate Permite programatorului s separe codul de gestiune al erorilor de restul codului. O excepie este un obiect aruncat pentru a reprezenta apariia unei erori. Poate fi de orice tip ce poate fi copiat dar se recomand a se utiliza doar tipuri definite de utilizator special pentru acel scop. Astfel se reduce ansa ca dou librrii diferite s foloseasc acelasi cod de eroare pentru a reprezenta erori diferite. Cel mai simplu mod de a defini o excepie este de a defini o clas special pentru acel tip de eroare Excepii
O excepie poate purta informaii despre eroarea pe care o reprezint
class RangeError {};
void f(int n) { if (n<0 || max<n) throw RangeError {}; // ... } Aruncarea i prinderea excepiilor Pot fi aruncate excepii de orice tip cu condiia s poate fi copiate class NoCopy { NoCopy(const NoCopy& x) = delete;//fara constructor de copiere }; class MyError{ // ... }; void f(int n){ MyError me; switch (n){ case 0: throw me; // OK break; case 3: throw MyError{}; // valabil doar in C++11 break; case 1: throw NoCopy{}; // eroare: nu se poate copia NoCopy break; case 2: throw MyError; // eroare: MyError este un tip break; } } Aruncarea i prinderea excepiilor Obiectul excepie este o copie a celui aruncat. Astfel este initializat o variabil temporar de tipul variabilei aruncate cu variabila aruncat. Aceast variabil poate fi copiat de cteva ori nainte de a fi prins: excepia este trimis napoi de la funcia apelata la funcia apelant pn cnd un handler potrivit este gsit. Tipul excepiei este folosit pentru pentru a selecta un handler al unui bloc try. Informaia dintr-un obiect excepie este folosit n mod obinuit pentru mesaje de eroare sau pentru help recovery Procesul de trimitere a excepiei este numit stack unwinding Aruncarea i prinderea excepiilor Avnd n vedere c o excepie este copiat de cteva ori nainte de a fi prins, nu se pun informaii mari n ea. Excepii ce conin cteva cuvinte sunt foarte comune. Unele excepii nu poart nici o informaie; numele tipului este suficient pentru a raporta o eroare. noexception Function Unele funcii nu arunc excepii iar unele chiar nu ar trebui. Pentru a indica acest lucru se poate declara funcia respectiv ca funcie noexcept
Este util pentru un programator deoarece nu trebuie sa mai scrie blocul try/catch pentru apelul funciei respective i de asemenea modulul de optimizare al compuilatorului nu mai are n vedere toat calea petru gsirea unui handler. Specificatorul noexcept spune doar c funcia nu gestioneaz excepii. double compute(double) noexcept; Prinderea excepiilor Fie:
Handlerul este invocat atunci cnd: H este de acelai tip cu E H este clasa de baz pentru E H si E sunt tipuri de date pointeri ctre H sau E
void f() { try { throw E{}; } catch(H) { // } } Dac un handler prinde o excepie i decide c nu o poate gestiona complet o poate rearunca. void f() { try { throw E{}; } catch(H) { if() {
double radical (double nr) { if (nr < 0.0) throw "Nu pot extrage radicalul unui nr negativ!"; return sqrt (nr); } Exemple
int main () { double nr; cin >> nr; try { cout << "Radical din " << nr << " este " << radical (nr); } catch (char *e) { cout << "Eroare: " << e << endl; } return 0; } Excepii netratate Considerm urmtoarea funcie main pentru exemplul de extragere a radicalului:
Funcia de extragere a radicalului ramne aceeai. int main () { double nr; cin >> nr;
cout << "Radical din " << nr<< " este " << radical (nr); return 0; } double radical (double nr) { if (nr < 0.0) throw "Nu pot extrage radicalul unui nr negativ!"; return sqrt (nr); } Excepii netratate La introducerea unei valori negative, rularea programului se va opri brusc i va fi semnalat o eroare:
Funciile pot lansa excepii care pot s nu aib nici o modalitate de tratare a lor Pentru a preveni aceste situaii, se poate introduce un handler catch-all: try { cout << "Radical din " << nr <<" este " << radical (nr); } catch (int e) { cout << "Eroare: " << e << endl; } catch (...) { cout << "Eroare necunoscuta" << endl; } Excepii n funcii membre Se folosesc n special n constructori pentru a semnala unele erori la crearea obiectelor Cnd se dorete semnalarea unei erori fr a duce la terminarea programului Excepii standard Librria c++ standard furnizeaz o clas de baz pentru tratarea obiectelor furnizate ca excepii Aceast clasa este std::exception Conine o funcie virtual what ce returneaz un char * int main() { try { while (true) { new int[100000000ul]; } } catch (const std::bad_alloc& e) { cout << "Allocation failed: " << e.what() << '\n'; } } http://en.cppreference.com/w/cpp/memory/new/bad_alloc