Cover
Börja nu gratis H2_SmartPointers.pdf
Summary
# nullptr en het gebruik ervan
Dit gedeelte introduceert nullptr als een type-veilige vervanging voor NULL en 0 bij het representeren van een nullpointer.
### 1.1 Introductie van nullptr
`nullptr` is een sleutelwoord dat een nullpointer constante voorstelt. Het is ontworpen om `NULL` en `0` te vervangen en is sterk getypeerd, wat betekent dat het specifiek bedoeld is voor gebruik met pointers. Vanaf nu wordt het gebruik van `nullptr` bij pointers verplicht gesteld [3](#page=3).
### 1.2 Voordelen van nullptr ten opzichte van NULL en 0
Het belangrijkste voordeel van `nullptr` is dat het sterk getypeerd is. Dit voorkomt ambiguïteit in situaties waar overbelasting van functies (function overloading) wordt gebruikt [3](#page=3).
> **Voorbeeld:** Overweeg de volgende functie declaraties:
> ```cpp
> void proc(int); //A
> void proc(int *); //B
> ```
> Als je `proc ` aanroept, zal de compiler `proc(int)` (A) kiezen omdat 0 kan worden geïnterpreteerd als een integer. Echter, als je `proc(nullptr)` aanroept, zal de compiler correct de `proc(int *)` (B) functie kiezen, omdat `nullptr` expliciet een pointer aangeeft. Dit leidt tot een fout of waarschuwing bij het gebruik van `NULL` of `0` in dergelijke contexten, terwijl `nullptr` correct wordt afgehandeld [3](#page=3).
### 1.3 Verplicht gebruik van nullptr
Het gebruik van `nullptr` is verplicht bij pointers vanaf C++11. Dit zorgt voor duidelijkere en veiligere code door het vermijden van potentiële type-mismatches en ambigue aanroepen van functies [3](#page=3).
---
# Algemene introductie tot smart pointers
Smart pointers zijn pointer-wrapperklassen die, naast de functionaliteit van een traditionele pointer, extra eigenschappen bieden, met name op het gebied van automatisch geheugenbeheer. Ze lossen de noodzaak van expliciete `new` en `delete` operaties op, wat traditionele raw pointers vereist en vaak leidt tot geheugenlekken of crashes. Hoewel ze iets minder efficiënt kunnen zijn dan raw pointers, bieden ze aanzienlijke voordelen in termen van geheugenveiligheid en codeonderhoudbaarheid. De C++ standaardbibliotheek biedt drie primaire smart pointer-klassen: `unique_ptr`, `shared_ptr` en `weak_ptr` [5](#page=5).
### 2.1 Wat zijn smart pointers?
Een smart pointer is in essentie een klasse die fungeert als een wrapper rond een raw pointer. Het voornaamste doel van deze wrapper is om het beheer van het geheugen waar de pointer naar verwijst te automatiseren. Dit omvat het automatisch toewijzen (reserveren) en vrijgeven van geheugen, wat een cruciaal aspect is in talen zoals C++ waar geheugenbeheer handmatig gebeurt [5](#page=5).
### 2.2 Rol in geheugenbeheer
Het handmatig beheren van geheugen met `new` en `delete` is een veelvoorkomende bron van fouten. Als een ontwikkelaar vergeet om geheugen vrij te geven na gebruik, ontstaat er een geheugenlek, wat leidt tot een toenemend geheugengebruik en uiteindelijk kan resulteren in instabiliteit van het programma. Omgekeerd, als geheugen te vroeg wordt vrijgegeven terwijl er nog pointers naar verwijzen, kan dit leiden tot *dangling pointers* en *undefined behavior*, wat eveneens tot crashes kan leiden. Smart pointers automatiseren dit proces, waardoor de kans op dergelijke fouten drastisch vermindert [5](#page=5).
### 2.3 Vergelijking met traditionele raw pointers
Traditionele raw pointers vereisen dat de programmeur expliciet verantwoordelijkheid neemt voor het toewijzen en vrijgeven van geheugen. Dit gebeurt met de `new` operator voor toewijzing en de `delete` operator (of `delete[]` voor arrays) voor vrijgave. Hoewel dit maximale controle biedt, is het ook foutgevoelig [5](#page=5).
Smart pointers bieden een veiliger alternatief. De in de documentatie genoemde klassen zijn:
* `unique_ptr`: Biedt exclusief eigendom over het beheerde geheugen. Slechts één `unique_ptr` kan tegelijkertijd naar een specifiek object verwijzen [5](#page=5).
* `shared_ptr`: Maakt gedeeld eigendom mogelijk. Meerdere `shared_ptr`s kunnen naar hetzelfde object verwijzen, en het geheugen wordt vrijgegeven wanneer de laatste `shared_ptr` die ernaar verwijst wordt vernietigd [5](#page=5).
* `weak_ptr`: Een complementaire smart pointer die een niet-eigenaarschapreferentie naar een object beheerd door een `shared_ptr` biedt. Het voorkomt *cyclic references* (wederzijdse verwijzingen) die zouden kunnen leiden tot geheugenlekken bij het gebruik van `shared_ptr` [5](#page=5).
Het gebruik van smart pointers elimineert de noodzaak voor handmatige `new` en `delete` aanroepen, wat leidt tot robuustere en onderhoudsvriendelijkere code [5](#page=5).
> **Tip:** Hoewel smart pointers het geheugenbeheer automatiseren, is het nog steeds belangrijk om te begrijpen hoe ze werken om te voorkomen dat je ze op de verkeerde manier gebruikt, vooral in complexe scenario's met gedeeld eigendom [5](#page=5).
---
# De unique_ptr
Dit gedeelte behandelt de `unique_ptr`, die een exclusief eigenaarschap van een beheerd object garandeert.
### 3.1 Eigenschappen van uniek eigenaarschap
Een `unique_ptr` wijst naar een object en garandeert dat er slechts één `unique_ptr` op elk moment eigenaar is van dat object. Dit unieke eigenaarschap impliceert dat `unique_ptr`-objecten niet gekopieerd kunnen worden. Wel zijn operaties zoals *move* en *swap* mogelijk [7](#page=7).
### 3.2 Maken en initialiseren van unique_ptrs
* Een `unique_ptr` kan geïnitialiseerd worden als `nullptr` [7](#page=7).
* De functie `make_unique(args...)` wordt gebruikt om een nieuw object van type `T` te creëren en een `unique_ptr` te retourneren die eigenaar wordt van dit object. De constructorargumenten voor `T` worden meegegeven aan `make_unique` [7](#page=7) [8](#page=8).
> **Voorbeeld:** `unique_ptr p1 = make_unique();` initialiseert `p1` om te wijzen naar een integer met de defaultwaarde. `unique_ptr p2 = make_unique;` initialiseert `p2` om te wijzen naar een integer met de waarde 20 [7](#page=7).
* `unique_ptr` kan ook gebruikt worden om te wijzen naar objecten van zelfgemaakte klassen [8](#page=8).
> **Voorbeeld:** `unique_ptr p1 = make_unique(r);` creëert een `unique_ptr` die eigenaar wordt van een kopie van het `rechthoek`-object `r`. `unique_ptr p2 = make_unique(6,3);` creëert een `unique_ptr` die eigenaar wordt van een nieuw `rechthoek`-object met dimensies 6 en 3 [8](#page=8).
### 3.3 Gebruik van dereferentie en member access operatoren
* Met de dereferentie-operator `*` kan de waarde van het object waar de `unique_ptr` naar wijst, worden benaderd [7](#page=7).
* Met de pijloperator `->` kunnen leden (methoden of variabelen) van het object waar de `unique_ptr` naar wijst, worden aangeroepen of benaderd [8](#page=8).
> **Voorbeeld:** `*p1 = 101;` wijst de waarde 101 toe aan het integer-object waar `p1` naar verwijst. `p1->print();` roept de `print()`-methode aan op het `rechthoek`-object waar `p1` naar verwijst [7](#page=7) [8](#page=8).
### 3.4 Move-semantiek en eigendomsoverdracht
* De *move*-operatie transfereert het eigenaarschap van het geheugen van de ene `unique_ptr` naar de andere. Na een move-operatie wordt de oorspronkelijke `unique_ptr` `nullptr` [7](#page=7).
> **Voorbeeld:** `p2 = move(p1);` maakt `p2` de nieuwe eigenaar van het geheugenobject waar `p1` naar wees. `p1` wordt na deze operatie `nullptr` [7](#page=7).
### 3.5 Geheugen vrijgeven
* Het bewaakte geheugen wordt automatisch vrijgegeven wanneer de `unique_ptr` buiten scope gaat [7](#page=7).
* De `reset()`-methode kan expliciet worden aangeroepen om het geheugen onmiddellijk vrij te geven. Na het aanroepen van `reset()` wordt de `unique_ptr` `nullptr` [7](#page=7).
> **Voorbeeld:** `p2.reset();` geeft het geheugen vrij waar `p2` naar wees, en `p2` wordt `nullptr` [7](#page=7).
### 3.6 Swap-operatie
* De `swap()`-methode wisselt de pointers tussen twee `unique_ptr`-objecten. Beide pointers blijven geldig, maar wijzen nu naar elkaars oorspronkelijke object [7](#page=7).
> **Voorbeeld:** `p1.swap(p2);` zorgt ervoor dat `p1` nu wijst naar wat `p2` eerst wees, en vice versa [7](#page=7).
### 3.7 Oefening met unieke pointers
Gegeven twee `unique_ptr`s, `p1` en `p2`, die naar elementen van hetzelfde, ongespecificeerde type wijzen, met de aanname dat dit type de operator `<` ondersteunt. Schrijf een procedure `kleinste_eerst(p1, p2)` die ervoor zorgt dat `p1` naar het kleinste element wijst en `p2` naar het grootste element van de twee [9](#page=9).
> **Tip:** Gebruik de dereferentie-operator en de swap-operatie om de pointers correct te herordenen [7](#page=7) [9](#page=9).
---
# De shared_ptr
Dit deel behandelt de functionaliteit van `shared_ptr`, de mogelijkheden voor gedeeld eigenaarschap, en het beheer van het vrijgeven van geheugen wanneer er meerdere referenties zijn [11](#page=11).
### 4.1 Gedeeld eigenaarschap met shared_ptr
Een `shared_ptr` maakt gedeeld eigenaarschap van een dynamisch gealloceerd object mogelijk. Dit betekent dat meerdere `shared_ptr` instanties kunnen verwijzen naar hetzelfde object in het geheugen [11](#page=11).
#### 4.1.1 Kenmerken van shared_ptr
* **Kopieerbaarheid:** `shared_ptr` instanties kunnen gekopieerd worden, waardoor er meerdere eigenaars van het object kunnen ontstaan [11](#page=11).
* **Geheugenbeheer:** Het geheugen waarnaar verwezen wordt, wordt pas vrijgegeven wanneer *alle* instanties van de `shared_ptr` die eigenaar zijn van het object, vernietigd zijn. Dit is een cruciaal verschil met raw pointers of `unique_ptr` [11](#page=11).
* **Overhead:** Het gebruik van `shared_ptr` brengt een bepaalde overhead met zich mee omdat het aantal referenties naar het object bijgehouden moet worden. Daarom wordt aangeraden `shared_ptr` alleen te gebruiken wanneer gedeeld eigenaarschap echt noodzakelijk is [11](#page=11).
#### 4.1.2 Gebruik en reset van shared_ptr
Een `shared_ptr` wordt typisch geïnitialiseerd met een object gecreëerd via `make_shared`. Wanneer een `shared_ptr` wordt toegewezen aan een andere `shared_ptr`, delen beide nu het eigenaarschap. Het vrijgeven van het geheugen gebeurt automatisch wanneer de laatste `shared_ptr` die het object beheert, buiten scope gaat of gereset wordt [11](#page=11).
> **Tip:** Gebruik `make_shared` in plaats van `new` gevolgd door een `shared_ptr` constructor, omdat `make_shared` efficiënter is en het aantal geheugenallocaties kan reduceren [11](#page=11).
> **Voorbeeld:**
> ```cpp
> #include
>
> // Initialisatie van twee shared_ptr instanties die nullptr zijn.
> std::shared_ptr p1, p2;
>
> // p1 krijgt eigenaarschap van een integer met waarde 101.
> p1 = std::make_shared ;
>
> // p2 wordt nu ook eigenaar van hetzelfde object als p1.
> // Beide p1 en p2 wijzen naar dezelfde geheugenlocatie.
> p2 = p1;
>
> // p2.reset() maakt p2 los van het object, maar het geheugen wordt nog NIET vrijgegeven
> // omdat p1 nog steeds eigenaar is.
> p2.reset();
>
> // p1.reset() maakt p1 los van het object. Nu zijn er geen shared_ptr instanties meer
> // die het object beheren, dus het geheugen wordt automatisch vrijgegeven.
> // Dit gebeurt ook automatisch als p1 uit scope gaat.
> p1.reset();
> ```
---
## 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 |
|------|------------|
| nullptr | Een sleutelwoord dat een nullpointer constante voorstelt. Het vervangt de oudere NULL en 0 constanten en is sterk getypeerd, wat betekent dat het enkel te gebruiken is bij pointers. |
| Smart pointer | Een klasse die fungeert als een wrapper om een pointer. Naast het beheren van de pointer zelf, biedt het extra functionaliteiten zoals automatisch reserveren en vrijgeven van geheugen, wat de noodzaak voor handmatige `new` en `delete` operaties elimineert. |
| unique_ptr | Een type smart pointer dat een uniek eigenaarschap over het beheerde geheugen garandeert. Het kan niet gekopieerd worden, maar ondersteunt wel `move` en `swap` operaties. Geheugen wordt automatisch vrijgegeven wanneer de `unique_ptr` buiten scope gaat. |
| shared_ptr | Een type smart pointer dat gedeeld eigenaarschap toestaat. Meerdere `shared_ptr` instanties kunnen naar hetzelfde geheugen wijzen. Het geheugen wordt pas vrijgegeven wanneer de laatste `shared_ptr` die ernaar verwijst, wordt vernietigd. |
| Raw pointers | Traditionele pointers in C++ die geen automatisch geheugenbeheer bieden. Gebruikers zijn zelf verantwoordelijk voor het toewijzen en vrijgeven van geheugen met `new` en `delete`. |
| Geheugen lek | Een situatie waarbij geheugen dat dynamisch is toegewezen, niet correct wordt vrijgegeven nadat het niet langer nodig is. Dit kan leiden tot een geleidelijke afname van beschikbare systeembronnen en prestatieproblemen. |
| Overhead | Extra rekenkracht of geheugen die nodig is om een bepaalde functionaliteit te implementeren. Bij `shared_ptr` ontstaat overhead door het bijhouden van het aantal referenties naar het beheerde geheugen. |
| Constructeur | Een speciale methode binnen een klasse die automatisch wordt aangeroepen wanneer een object van die klasse wordt gecreëerd. Het initialiseert de gegevensleden van het object. |