Cover
Start nu gratis H6_ExceptionHandling.pdf
Summary
# Exception handling in C++
Dit hoofdstuk introduceert het concept van exception handling in C++ en demonstreert hoe fouten op te vangen en af te handelen met behulp van try-catch blokken [1](#page=1) [2](#page=2).
## 1.1 Introductie tot exception handling
Traditionele C-stijl foutafhandeling maakt vaak gebruik van speciale returnwaarden om fouten aan te geven. Dit heeft echter nadelen [2](#page=2):
* Het is niet altijd mogelijk om een unieke speciale returnwaarde te definiëren die alle mogelijke foutcondities kan representeren [2](#page=2).
* Als de gebruiker van de functie de returnwaarde niet controleert, kan het programma doorgaan met een ongeldige status, wat leidt tot onvoorspelbaar gedrag [2](#page=2).
Exception handling biedt een robuustere en duidelijkere methode voor foutafhandeling in C++.
## 1.2 Het `throw`-sleutelwoord
Wanneer een fout optreedt die niet elegant afgehandeld kan worden binnen de functie zelf, kan deze een "exceptie" opwerpen met behulp van het `throw`-sleutelwoord [3](#page=3).
* Elk type, inclusief primitieve types zoals `int` en `char`, `string` objecten, of speciaal gedefinieerde exceptie-objecten, kan worden opgeworpen [3](#page=3).
* De functiesignatuur hoeft niet expliciet te vermelden dat er een exceptie kan worden opgeworpen [3](#page=3).
### 1.2.1 Voorbeeld: faculteitsfunctie
De faculteitsfunctie kan worden aangepast om een exceptie op te werpen wanneer een negatief getal wordt meegegeven:
```cpp
int fac(int getal) {
if (getal < 0)
throw "exceptie: negatief getal!"; // Werpt een C-string exceptie op
int res = 1;
for (int i = 2 ; i <= getal ; i++)
res *= i;
return res;
}
```
## 1.3 Het `try-catch`-mechanisme
Om een opgeworpen exceptie op te vangen en af te handelen, wordt gebruik gemaakt van `try-catch`-blokken [4](#page=4).
* De code waarvan verwacht wordt dat deze een exceptie zou kunnen opwerpen, wordt geplaatst binnen een `try`-blok [4](#page=4).
* Na het `try`-blok volgen één of meer `catch`-blokken, die elk een specifiek type exceptie kunnen opvangen [4](#page=4).
### 1.3.1 Vangen van specifieke fouten
Een `catch`-blok kan worden gespecificeerd voor een bepaald type, zoals een C-string:
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (const char *s) { // Vangt C-string excepties op
cout << s << endl;
}
return 0;
}
```
Er kunnen meerdere `catch`-blokken na elkaar staan om verschillende typen excepties te verwerken [4](#page=4).
### 1.3.2 Vangen van alle mogelijke fouten (`catch (...)`)
Als men alle mogelijke excepties wil opvangen, ongeacht hun type, kan een `catch (...)` blok worden gebruikt [5](#page=5).
* Dit "catch-all" blok moet altijd het laatste `catch`-blok zijn [5](#page=5).
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (...) { // Vangt alle excepties op
cout << "oei, een fout" << endl;
}
return 0;
}
```
## 1.4 Opmerkingen over exception handling
* **Variabelen in `catch`-clausules:** De declaratie in de `catch`-clausule hoeft niet noodzakelijk een variabele naam te bevatten; het type alleen is voldoende om een exceptie van dat type op te vangen [6](#page=6).
```cpp
catch (const char *) { cout << "ai ai ai"; }
```
* **`noexcept` sleutelwoord (C++11):** Sinds C++11 kan het sleutelwoord `noexcept` gebruikt worden in zowel de declaratie als de definitie van een functie om aan te geven dat deze functie geen excepties zal opwerpen. Dit kan door de compiler worden gebruikt voor optimalisaties [6](#page=6).
```cpp
int functie() noexcept;
```
## 1.5 Exception klassen in de standaardbibliotheek
De C++ standaardbibliotheek biedt een hiërarchie van exception klassen, die te vinden zijn in de `` header [8](#page=8).
* Sommige van deze klassen worden door bestaande standaardbibliotheekmethoden opgeworpen, zoals `out_of_range` of `invalid_argument` [8](#page=8).
* Andere klassen zijn bedoeld om door de programmeur zelf te worden gebruikt bij het opwerpen van specifieke foutcondities, zoals `domain_error` voor wiskundige domeinfouten [8](#page=8).
De belangrijkste exception klassen omvatten:
* `exception` (de basisklasse) [8](#page=8).
* Afgeleid van `exception`:
* `logic_error` (fouten in de programmalogica) [8](#page=8).
* `runtime_error` (fouten die pas tijdens de uitvoering optreden) [8](#page=8).
* Afgeleid van `logic_error`:
* `domain_error`
* `invalid_argument`
* `length_error`
* `out_of_range`
* Afgeleid van `runtime_error`:
* `overflow_error`
* `range_error`
* `underflow_error`
* Andere speciale exception klassen zoals `bad_exception`, `bad_typeid`, `bad_cast`, `bad_alloc` [8](#page=8).
### 1.5.1 Voorbeeld: Gebruik van `out_of_range`
Wanneer bijvoorbeeld een index buiten de grenzen van een `std::vector` wordt benaderd met de `.at()` methode, wordt een `out_of_range` exceptie opgeworpen [9](#page=9).
```cpp
#include
#include
#include
using namespace std;
int main() {
vector v = {8, 10, 12};
try {
// Poging om een element buiten de vector te benaderen
int i = v.at(v.size()); // v.size() is 3, geldige indices zijn 0, 1, 2
} catch (const out_of_range& e) {
// Vangt de out_of_range exceptie op
cout << "Exceptie: " << e.what() << endl; // e.what() geeft de beschrijving van de fout
}
return 0;
}
```
De methode `.what()` van een exception object geeft een beschrijvende string van de fout [9](#page=9).
## 1.6 Zelfgemaakte exception klassen
Het is mogelijk en vaak wenselijk om eigen exception klassen te definiëren, meestal door te erven van bestaande standaard exception klassen zoals `runtime_error` of `logic_error`. Dit zorgt voor een gestructureerde en herkenbare foutafhandeling [10](#page=10).
* De basisklasse `exception` heeft geen constructor die een `std::string` of C-style string als parameter accepteert. Daarom is het aan te raden om te erven van `runtime_error` of `logic_error` [10](#page=10).
### 1.6.1 Voorbeeld: `file_error` klasse
Een `file_error` klasse kan worden gemaakt om fouten gerelateerd aan bestandsoperaties te signaleren:
```cpp
// file_error.h
#include
#include
using namespace std;
class file_error : public runtime_error {
public:
// Constructor die de standaardfoutmelding gebruikt
file_error() : runtime_error("can't open file") {}
// Constructor die een specifieke foutmelding accepteert
file_error(const string &what) : runtime_error(what) {}
};
```
Deze zelfgemaakte klasse kan vervolgens worden opgeworpen en opgevangen:
```cpp
// vb_excep3.cpp
#include "file_error.h"
#include
#include
#include
using namespace std;
void openFile(const string &fname, ifstream& in) {
in.open(fname);
if (!in) {
// Werpt een eigen file_error exceptie op met een specifieke melding
throw file_error("Can't open file " + fname);
}
}
int main() {
string file_name = "niet_bestaand.txt";
ifstream inv;
try {
openFile(file_name, inv);
// Verdere verwerking van het bestand...
inv.close();
} catch (const file_error& fe) { // Vangt de zelfgemaakte exceptie op
cout << fe.what() << endl; // Toont de foutmelding
}
return 0;
}
```
> **Tip:** Door eigen exception klassen te definiëren, wordt de code leesbaarder en is het voor andere ontwikkelaars duidelijker welke soorten fouten er door een specifieke module kunnen worden afgehandeld.
## 1.7 Het `finally`-concept (conceptueel)
Hoewel C++ geen expliciet `finally`-sleutelwoord heeft zoals sommige andere talen, kan een vergelijkbaar effect worden bereikt door gebruik te maken van RAII (Resource Acquisition Is Initialization). Dit wordt vaak geïmplementeerd met behulp van destructors van objecten die binnen een `try`-blok worden aangemaakt. Wanneer een exceptie wordt opgeworpen, worden de destructors van de automatische objecten op de call stack uitgevoerd in omgekeerde volgorde van hun creatie, wat garandeert dat resources (zoals geopende bestanden of geheugen) worden opgeruimd [11](#page=11).
> **Tip:** Gebruik destructors om de opruiming van resources te garanderen, zelfs als er excepties optreden. Dit is de C++ manier om het 'finally'-gedrag te bereiken.
---
# Opvangen van specifieke en algemene fouten
Het opvangen van fouten in code is essentieel voor robuuste softwareontwikkeling, waarbij het mogelijk is om zowel specifieke als algemene fouten af te handelen. Dit wordt bereikt door het gebruik van meerdere `catch`-blokken voor specifieke fouttypen of een `catch-all` handler voor alle overige fouten [4](#page=4) [5](#page=5).
### 2.1 Opvangen van specifieke fouttypen
Wanneer specifieke foutcondities verwacht worden, kunnen meerdere `catch`-blokken na een `try`-blok worden geplaatst. Elk `catch`-blok is specifiek ontworpen om een bepaald type exception af te handelen. Dit maakt het mogelijk om gedifferentieerd te reageren op diverse fouten die tijdens de uitvoering kunnen optreden [4](#page=4).
**Voorbeeld:**
Stel dat een functie `fac` (waarschijnlijk voor faculteit) een `const char *` exception werpt bij een ongeldige invoer. In dit geval kan een `catch`-blok worden gedefinieerd om dit specifieke type exception op te vangen.
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (const char *s) {
cout << s << endl;
}
return 0;
}
```
Dit voorbeeld toont hoe de code specifieke C-string fouten kan opvangen [4](#page=4).
### 2.2 Opvangen van alle mogelijke fouten
Naast het specifiek afhandelen van bekende fouttypen, is het ook mogelijk om alle overige, onbekende exceptions te vangen met behulp van een `catch-all` handler. Deze handler wordt aangeduid met drie puntjes (`...`) binnen de `catch`-declaratie [5](#page=5).
**Belangrijk:** De `catch-all` handler moet altijd het **laatste** `catch`-blok zijn na de meer specifieke handlers. Als deze eerder wordt geplaatst, zullen de specifiekere blokken nooit bereikt worden [5](#page=5).
**Voorbeeld:**
In het volgende codefragment wordt getoond hoe een `catch-all` handler gebruikt kan worden om elke exception die niet door eerdere `catch`-blokken wordt afgehandeld, op te vangen.
```cpp
int main() {
int getal;
cin >> getal;
try {
cout << fac(getal) << endl;
}
catch (...) {
cout << "oei, een fout" << endl;
}
return 0;
}
```
Deze opzet vangt elke mogelijke fout op, ongeacht het type, en geeft een generieke foutmelding weer. Dit is nuttig voor het garanderen dat er altijd een fallback is voor onvoorziene problemen [5](#page=5).
---
# Standaard en zelfgemaakte exception klassen
Dit onderwerp behandelt het gebruik van ingebouwde exception klassen uit de C++ standaardbibliotheek en de constructie van eigen, aangepaste exception klassen die afgeleid zijn van `runtime_error` of `logic_error` [8](#page=8).
### 3.1 Overzicht van exception klassen
De C++ standaardbibliotheek biedt een hiërarchie van exception klassen, georganiseerd onder de basisklasse `exception`. Deze klassen worden gebruikt om specifieke foutcondities tijdens de uitvoering van een programma te signaleren [8](#page=8).
#### 3.1.1 Categorieën van standaard exceptions
De standaard exceptions kunnen grofweg worden onderverdeeld in twee hoofdcategorieën, gebaseerd op de klasse waarvan ze afgeleid zijn:
* **`logic_error`**: Deze exceptions signaleren fouten die logischerwijs tijdens de uitvoering van het programma ontdekt hadden kunnen worden, zelfs zonder daadwerkelijk optreden van foutieve systeemtoestanden. Voorbeelden hiervan zijn [8](#page=8):
* `domain_error`: Geeft aan dat een functie een argument heeft ontvangen dat buiten het toegestane domein valt [8](#page=8).
* `invalid_argument`: Geeft aan dat een functie een ongeldig argument heeft ontvangen [8](#page=8).
* `length_error`: Geeft aan dat een poging is gedaan om een object te creëren dat te groot is [8](#page=8).
* `out_of_range`: Geeft aan dat een index of positie buiten de geldige bereiken valt [8](#page=8).
* `overflow_error`: Geeft aan dat een rekenkundige operatie heeft geleid tot een waarde die te groot is om gerepresenteerd te worden [8](#page=8).
* `range_error`: Geeft aan dat een rekenkundige operatie heeft geleid tot een waarde die te klein is om gerepresenteerd te worden [8](#page=8).
* `underflow_error`: Geeft aan dat een rekenkundige operatie heeft geleid tot een waarde die te klein is om gerepresenteerd te worden [8](#page=8).
* **`runtime_error`**: Deze exceptions signaleren fouten die optreden tijdens de uitvoering van het programma en die niet noodzakelijkerwijs direct voortvloeien uit de logica van de algoritmes, maar eerder uit de interactie met de omgeving of onverwachte toestanden. Voorbeelden zijn [8](#page=8):
* `bad_alloc`: Wordt opgeworpen bij een mislukte geheugenallocatie [8](#page=8).
* `bad_cast`: Wordt opgeworpen bij een mislukte `dynamic_cast` naar een referentie [8](#page=8).
* `bad_typeid`: Wordt opgeworpen bij een mislukte `typeid` operatie [8](#page=8).
* `bad_exception`: Wordt opgeworpen wanneer een exception wordt gegenereerd die niet gespecificeerd is in de `noexcept` specificatie [8](#page=8).
> **Tip:** Veel standaardmethoden, zoals die van `std::vector` (bv. `.at()`), kunnen automatisch exceptions zoals `out_of_range` opwerpen wanneer incorrecte indexen worden gebruikt [9](#page=9).
#### 3.1.2 Gebruik van bestaande exception klassen
Om standaard exceptions te gebruiken, is het noodzakelijk de `` header in te sluiten. Een `try-catch` blok is de standaardmethode om exceptions te verwerken. Binnen het `try` blok wordt code geplaatst die mogelijk een exception kan opwerpen. In het `catch` blok wordt specifiek de exception afgehandeld [9](#page=9).
> **Example:** Het opvangen van een `out_of_range` exception bij het benaderen van een `std::vector` buiten zijn grenzen.
> ```cpp
> #include
> #include
> #include
>
> using namespace std;
>
> int main() {
> vector v = {8, 10, 12};
> try {
> // Poging om een element buiten het bereik van de vector te benaderen
> int i = v.at(v.size()); // v.size() is 3, index 3 is buiten bereik voor vector met grootte 3
> } catch (const out_of_range& e) {
> // De exception wordt hier opgevangen en de melding wordt uitgeprint
> cout << "Exceptie: " << e.what() << endl;
> }
> return 0;
> }
> ```
> In dit voorbeeld zal de `cout` regel "Exceptie: vector::_M_range_check: __n (which is 3) >= this->size() (which is 3)" (of een vergelijkbare melding afhankelijk van de compiler) uitvoeren [9](#page=9).
### 3.2 Zelfgemaakte exception klassen
Hoewel de standaardbibliotheek een breed scala aan exceptions biedt, kan het noodzakelijk zijn om eigen exception klassen te definiëren voor specifieke foutcondities die uniek zijn voor de applicatie [10](#page=10).
#### 3.2.1 Principes voor het maken van zelfgemaakte exceptions
Zelfgemaakte exception klassen worden typisch afgeleid van bestaande standaard exception klassen, zoals `runtime_error` of `logic_error`. Dit zorgt voor een consistente foutafhandeling en maakt het mogelijk om de standaard mechanismen van C++ exceptions te benutten. De basisklasse `exception` zelf heeft geen constructor die een `std::string` of C-string als parameter accepteert, vandaar de noodzaak om af te leiden van `runtime_error` of `logic_error`, die deze functionaliteit wel bieden [10](#page=10).
#### 3.2.2 Implementatie van een zelfgemaakte exception
Een zelfgemaakte exception klasse kan worden gecreëerd door de gewenste standaard exception klasse te overerven. De constructor van de zelfgemaakte klasse kan vervolgens de constructor van de basisklasse aanroepen om een specifieke foutmelding te initialiseren [10](#page=10).
> **Example:** Een `file_error` klasse, afgeleid van `runtime_error`, voor het signaleren van problemen bij het openen van bestanden.
> ```cpp
> // file_error.h
> #include
> #include
>
> using namespace std;
>
> class file_error : public runtime_error {
> public:
> // Default constructor met een standaard melding
> file_error() : runtime_error("can't open file") {}
>
> // Constructor die een specifieke melding accepteert
> file_error(const string &what) : runtime_error(what) {}
> };
> ```
In een functie die mogelijk een `file_error` kan veroorzaken, kan deze exception worden opgeworpen met behulp van de `throw` sleutelwoord wanneer de foutconditie optreedt [11](#page=11).
> **Example:** Een functie `openFile` die een `file_error` opwerpt als het bestand niet geopend kan worden.
> ```cpp
> // vb_excep3.cpp
> #include "file_error.h" // Veronderstelt dat file_error.h hierboven is gedefinieerd
> #include
> #include
> #include
>
> using namespace std;
>
> void openFile(const string &fname, ifstream& in) {
> in.open(fname);
> if (!in) {
> // Opwerpen van de zelfgemaakte exception met een specifieke melding
> throw file_error("Can't open file " + fname);
> }
> }
>
> int main() {
> string file_name = "nonexistent.txt";
> ifstream inv;
> try {
> openFile(file_name, inv);
> // Verdere code als het bestand succesvol geopend is
> // ...
> inv.close();
> } catch (const file_error& fe) {
> // Opvangen van de specifieke file_error
> cout << "Bestandsfout: " << fe.what() << endl;
> } catch (const exception& e) {
> // Opvangen van andere mogelijke exceptions
> cout << "Andere fout: " << e.what() << endl;
> }
> return 0;
> }
> ```
> Wanneer `nonexistent.txt` niet bestaat, zal de output zijn: "Bestandsfout: Can't open file nonexistent.txt" [11](#page=11).
Door eigen exception klassen te definiëren, kan de code robuuster worden gemaakt door specifieke foutcondities duidelijk te signaleren en te behandelen, wat leidt tot beter leesbare en onderhoudbare programma's.
---
## Veelgemaakte fouten om te vermijden
- Bestudeer alle onderwerpen grondig voor examens
- Let op formules en belangrijke definities
- Oefen met de voorbeelden in elke sectie
- Memoriseer niet zonder de onderliggende concepten te begrijpen
Glossary
| Term | Definition |
|------|------------|
| Exception handling | Een mechanisme in programmeertalen om fouten of uitzonderlijke condities tijdens de uitvoering van een programma op te vangen en af te handelen. Dit voorkomt dat het programma crasht. |
| Opwerpen (throw) | Het proces waarbij een foutconditie wordt aangegeven en een exception object wordt gecreëerd en doorgegeven in het programma. |
| Opvangen (catch) | Het proces waarbij een exception object dat is opgeworpen, wordt onderschept en behandeld door een specifiek blok code, vaak na een try-blok. |
| Try-blok | Een codeblok waarin potentieel foutgevoelige operaties worden uitgevoerd. Als er een exception wordt opgeworpen binnen dit blok, wordt de uitvoering ervan onmiddellijk onderbroken. |
| Catch-blok | Een codeblok dat wordt uitgevoerd wanneer een exception van een bepaald type wordt opgevangen. Meerdere catch-blokken kunnen na een try-blok komen om verschillende soorten exceptions af te handelen. |
| Catch-all handler | Een speciaal catch-blok (aangegeven met `...`) dat elke exception kan opvangen, ongeacht het type. Dit wordt meestal als laatste catch-blok geplaatst. |
| `noexcept` | Een sleutelwoord in C++ (vanaf C++11) dat aangeeft dat een functie geen exceptions zal opwerpen. Dit kan door de compiler worden gebruikt voor optimalisaties. |
| `stdexcept` | Een headerbestand in de C++ Standard Library dat een set standaard exception klassen bevat, zoals `runtime_error` en `logic_error`. |
| `runtime_error` | Een standaard exception klasse die fouten aangeeft die optreden tijdens de uitvoering van het programma, bijvoorbeeld het niet kunnen openen van een bestand. |
| `logic_error` | Een standaard exception klasse die fouten aangeeft die logische problemen in de code zelf impliceren, bijvoorbeeld een ongeldige argumentwaarde. |
| `out_of_range` | Een standaard exception klasse die wordt opgeworpen wanneer een poging wordt gedaan om toegang te krijgen tot een element buiten de geldige grenzen van een container, zoals een vector of string. |
| `invalid_argument` | Een standaard exception klasse die wordt opgeworpen wanneer een functie een argument ontvangt dat ongeldig is, zelfs als het van het juiste type is. |
| `exception` | De basisklasse voor alle standaard exceptions in C++. Andere exception klassen erven hiervan af. |