Crearea propriilor tipuri de excepții
Pentru anumite sarcini specifice, putem crea propriile tipuri de excepții, ceea ce ne permite să transmitem informații mai structurate și complexe despre eroare decât dacă am folosi tipuri primitive. De exemplu, să analizăm următorul program:
#include <iostream>
class AgeException
{
public:
AgeException(std::string message): message{message}{}
std::string getMessage() const {return message;}
private:
std::string message;
};
class Person
{
public:
Person(std::string name, unsigned age)
{
if(!age||age>110)
{
throw AgeException{"Invalid age"};
}
this->name = name;
this->age = age;
}
void print() const
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
private:
std::string name;
unsigned age;
};
int main()
{
try
{
Person tom{"Tom", 38};
tom.print();
Person bob{"Bob", 1500};
bob.print();
}
catch (const AgeException& ex)
{
std::cout << ex.getMessage() << std::endl;
}
}
Aici este definită clasa Person, al cărei constructor primește numele și vârsta utilizatorului. Totuși, este necesar ca vârsta să fie într-un interval rezonabil, de exemplu între 1 și 110. În acest caz, în constructor verificăm valoarea transmisă. Dacă depășește limitele permise, generăm o excepție de tipul clasei AgeException.
throw AgeException{"Invalid age"};
Clasa AgeException a fost creată special pentru a încapsula o excepție legată de vârsta unei persoane. Aceasta stochează un mesaj de eroare și oferă metoda getMessage pentru a accesa mesajul.
În blocul try-catch, sunt testați câțiva obiecte Person. La transmiterea unei vârste incorecte:
Person bob{"Bob", 1500};
se va genera o excepție de tip AgeException, iar execuția va trece în blocul catch care gestionează acest tip:
catch (const AgeException& ex)
{
std::cout << ex.getMessage() << std::endl;
}
Pentru a evita copierea inutilă a obiectului de excepție, acesta este transmis prin referință.
Rezultatul afișat va fi:
Name: Tom Age: 38
Invalid age
Ordinea tratării excepțiilor de tip bază și derivat
Când apare o excepție, blocurile catch sunt analizate în ordinea în care sunt definite în cod. Dacă se găsește primul bloc catch al cărui parametru corespunde tipului excepției, acesta va fi ales pentru tratarea ei. Pentru excepțiile de tipuri primitive este necesară potrivirea exactă. Pentru obiectele de tip clasă, pot fi aplicate conversii implicite.
Un bloc catch este potrivit dacă:
- Parametrul are același tip ca și excepția (ignorând const)
- Tipul parametrului este clasa de bază a excepției
- Tipul parametrului este pointer la clasa de bază și excepția este un pointer la clasa derivată
Astfel, putem prinde toate excepțiile de tip bază și derivat folosind un singur bloc catch.
#include <iostream>
class AgeException
{
public:
AgeException(std::string message): message{message}{}
virtual std::string getMessage() const
{
return message;
}
private:
std::string message;
};
class MaxAgeException: public AgeException
{
public:
MaxAgeException(std::string message, unsigned maxAge): AgeException{message}, maxAge{maxAge}
{}
std::string getMessage() const override
{
return AgeException::getMessage() + " Max age should be " + std::to_string(maxAge);
}
private:
unsigned maxAge;
};
class Person
{
public:
Person(std::string name, unsigned age)
{
if(!age)
{
throw AgeException{"Invalid age"};
}
if(age>110)
{
throw MaxAgeException{"Invalid age.", 110};
}
this->name = name;
this->age = age;
}
void print() const
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
private:
std::string name;
unsigned age;
};
int main()
{
try
{
Person bob{"Bob", 1500};
bob.print();
}
catch (const AgeException& ex)
{
std::cout << ex.getMessage() << std::endl;
}
}
Pentru cazurile în care vârsta depășește maximul permis, este definită clasa MaxAgeException, derivată din AgeException, care reține limita superioară și suprascrie metoda getMessage.
Chiar dacă în constructorul clasei Person sunt aruncate separat cele două tipuri de excepții:
if(!age)
{
throw AgeException{"Invalid age"};
}
if(age>110)
{
throw MaxAgeException{"Invalid age.", 110};
}
putem trata ambele cu un singur catch pentru clasa de bază:
catch (const AgeException& ex)
{
std::cout << ex.getMessage() << std::endl;
}
Fiindcă getMessage este o funcție virtuală și parametrul este transmis prin referință, se va apela implementarea corectă. Rezultatul va fi:
Invalid age. Max age should be 110
Tratarea separată a excepțiilor
Uneori este necesar să tratăm separat excepțiile clasei de bază și cele ale clasei derivate, mai ales când vrem să apelăm metode care există doar în clasa derivată. Întrucât obiectele derivate se pot potrivi și cu parametrii clasei de bază, blocul catch pentru clasa derivată trebuie pus înainte celui pentru clasa de bază.
void testPerson(std::string name, unsigned age)
{
try
{
Person person{name, age};
person.print();
}
catch (const MaxAgeException& ex)
{
std::cout << "MaxAgeException: " << ex.getMessage() << std::endl;
}
catch (const AgeException& ex)
{
std::cout << "AgeException: " << ex.getMessage() << std::endl;
}
}
int main()
{
testPerson("Tom", 0); // AgeException: Invalid age
testPerson("Bob", 1000); // MaxAgeException: Invalid age. Max age should be 110
}