Cover
Start nu gratis H7_Afsluiter.pdf
Summary
# Automatische type-afleiding met auto
Het `auto` sleutelwoord in C++11 faciliteert automatische type-afleiding, wat vooral handig is bij het werken met complexe datatypen [3](#page=3).
### 1.1 Werking van `auto`
Sinds C++11 kan het `auto` sleutelwoord worden gebruikt wanneer een variabele declaratie vergezeld wordt van een initialisatie. Het `auto` sleutelwoord instrueert de compiler om het type van de variabele af te leiden op basis van de initializer [3](#page=3).
#### 1.1.1 Syntaxis en voorbeelden
De basis syntaxis is:
`auto variable_name = initializer;` [3](#page=3).
Hier zijn enkele eenvoudige voorbeelden:
* `auto a = 0;` zal de variabele `a` declareren als een `int` [3](#page=3).
* `auto b = 'a';` zal de variabele `b` declareren als een `char` [3](#page=3).
> **Tip:** Het gebruik van `auto` is optioneel; het is niet verplicht. Echter, bij het initialiseren van een variabele met `auto`, is het type van de initializer cruciaal voor de afleiding [3](#page=3).
### 1.2 Nut van `auto` bij complexe types
Het ware nut van `auto` komt tot uiting bij het omgaan met complexe datatypen, zoals die vaak voorkomen in de Standard Template Library (STL). Het declareren van variabelen van deze complexe types kan leiden tot lange en potentieel foutgevoelige declaraties [3](#page=3).
#### 1.2.1 STL iteratoren
Voorheen moest men bij STL containers zoals `std::set` of `std::map` expliciet het type van iteratoren declareren. Dit type kan erg lang en moeilijk te lezen zijn, vooral wanneer templates gebruikt worden. Beschouw de volgende declaratie zonder `auto`:
```cpp
template
void proc(const std::set &s) {
typename std::set::const_iterator ci = s.begin();
// ... verdere code ...
}
```
Hier moet `typename std::set::const_iterator` de volledige type-declaratie zijn [3](#page=3).
Met `auto` kan deze declaratie aanzienlijk vereenvoudigd worden:
```cpp
template
void proc(const std::set &s) {
auto ci = s.begin();
// ... verdere code ...
}
```
De compiler leidt nu af dat `ci` het type `typename std::set::const_iterator` moet hebben, gebaseerd op de initializer `s.begin()` [3](#page=3).
#### 1.2.2 Range-based for loops
Een ander belangrijk toepassingsgebied is de range-based for loop, geïntroduceerd in C++11. Bij het doorlopen van complexe datastructuren, zoals een map van integers naar vectors van strings, kan het afleiden van het type van het element dat per iteratie wordt verwerkt, een uitkomst zijn.
Beschouw de volgende declaratie van een map:
`std::map> m;` [3](#page=3).
Een traditionele for-loop om elk element `el` te verwerken, zou de volgende declaratie vereisen:
`for (const std::pair>& el: m)` [3](#page=3).
Met `auto` wordt dit aanzienlijk leesbaarder:
`for (const auto &el: m) {... }` [3](#page=3).
De compiler zal automatisch het type van `el` afleiden als `const std::pair>&`, wat de code compacter en minder vatbaar voor typfouten maakt [3](#page=3).
> **Tip:** Het gebruik van `auto` in combinatie met een referentie (`&`) en `const` is vaak een goede praktijk, vooral in range-based for loops, om onnodige kopieën te voorkomen en de efficiëntie te waarborgen [3](#page=3).
---
# Wijzigingen in initialisatiesyntax
Dit onderwerp behandelt de introductie van een uniforme initialisatiesyntax met accolades in C++11, inclusief de mogelijkheden voor in-klasse initialisatie van attributen [5](#page=5).
### 2.1 Traditionele initialisatiesyntax
Vóór C++11 kende C++ verschillende, niet-uniforme notaties voor initialisatie:
* **Constructor-initialisatie met haakjes:** Dit werd gebruikt voor objecten, bijvoorbeeld `string s("hello");` [5](#page=5).
* **Accolades voor structs en arrays:** Voor het initialiseren van structs en arrays werden accolades gebruikt, zoals `int t = {0,1,2,3};` [4](#page=4) [5](#page=5).
* **Initializer list bij definitie van constructoren:** Class-leden konden worden geïnitialiseerd via een initializer list in de constructor-definitie, bijvoorbeeld `A(): x {}` [5](#page=5).
### 2.2 Uniforme initialisatiesyntax met accolades in C++11
C++11 introduceerde een uniforme initialisatiesyntax met accolades om de inconsistenties te verhelpen. Deze nieuwe syntax, ook wel *brace-initialization* genoemd, kan voor diverse typen worden gebruikt [6](#page=6):
* **Class-leden initialisatie:** Accolades kunnen worden gebruikt om leden van een klasse te initialiseren, zowel met default-waarden als bij het creëren van instanties [6](#page=6).
**Voorbeeld:**
> In het volgende voorbeeld worden `x` en `y` geïnitialiseerd met accolades binnen de constructor en de klasse definitie:
> ```cpp
> class A {
> int x;
> int y [4](#page=4);
> public:
> A(int _x=0) : x{_x}, y{1,2,3,4} {}
> };
> ```
> [6](#page=6).
* **Variabele-initialisatie:** Variabelen van basistypen en standaard-containerklassen kunnen met accolades worden geïnitialiseerd [6](#page=6).
**Voorbeeld:**
> In `main` wordt een instantie `a` van `A` geïnitialiseerd met `A a{3};` en een integer `b` met `int b{2};`. Een `std::vector` wordt geïnitialiseerd met `vector v = {10,20};` [6](#page=6).
* **Belangrijke nuanceringen:**
* `A a();` definieert een functie genaamd `a` die geen argumenten neemt en een `A` retourneert, en is **fout** als bedoeling om een object te creëren en te initialiseren [6](#page=6).
* `A a{};` creëert een object van type `A` en initialiseert de leden met hun default-waarden of via de constructor indien van toepassing (value-initialization voor fundamentele typen) [6](#page=6).
* `A a;` is een standaard objectcreatie en kan ook als correct worden beschouwd [6](#page=6).
* `int b = 2;` is de traditionele syntax, die nu kan worden vervangen door `int b{2};` [6](#page=6).
* `vector v{10,20};` is een directe initialisatie van de vector met de opgegeven elementen [6](#page=6).
* `vector v({10,20});` is ook een geldige vorm van initialisatie voor `std::vector` [6](#page=6).
* `vector v(10,20);` creëert een vector met 10 elementen, elk met de waarde 20, wat verschilt van het bedoelde gebruik van de uniforme initialisatiesyntax voor een lijst van elementen [6](#page=6).
### 2.3 In-klasse initialisatie van attributen
Sinds C++11 is het toegestaan om attributen (leden van een klasse) direct in de klasse-definitie te initialiseren. Deze initialisatie gebeurt voordat de constructor wordt aangeroepen [7](#page=7).
* **Syntax:** Attributen kunnen worden geïnitialiseerd met een gelijkteken (`=`) gevolgd door een waarde, of met accolades (`{}`) gevolgd door een waarde [7](#page=7).
**Voorbeeld:**
> In de klasse `A` worden de attributen `x` en `y` direct geïnitialiseerd:
> ```cpp
> class A {
> private:
> int x = 7;
> int y{9};
> public:
> A() {}
> };
> ```
> Hier wordt `x` geïnitialiseerd tot `7` en `y` tot `9` [7](#page=7).
* **Let op:** De traditionele constructor-syntax met haakjes `int y;` is **niet toegestaan** voor in-klasse initialisatie van leden; dit zou een compilerfout opleveren [7](#page=7) .
> **Tip:** De mogelijkheid tot in-klasse initialisatie vereenvoudigt de code en zorgt ervoor dat leden van een object altijd een gedefinieerde begintoestand hebben, zelfs als de constructor geen specifieke initialisatielijst bevat [7](#page=7).
---
# Defaulted en deleted lidfuncties
Dit onderwerp behandelt twee speciale functies in C++: defaulted lidfuncties, die de compiler instrueren om de standaardimplementatie te genereren, en deleted lidfuncties, die automatische generatie expliciet verbieden [9](#page=9).
### 3.1 Defaulted lidfuncties
Een defaulted lidfunctie, aangegeven met `= default;`, instrueert de compiler expliciet om de standaardimplementatie van die lidfunctie te genereren. Dit is nuttig wanneer je expliciet wilt aangeven dat je de door de compiler voorgestelde standaardgedrag wilt behouden, bijvoorbeeld voor constructors, copy-constructors, move-constructors, en destructors [9](#page=9).
**Voorbeeld van een defaulted lidfunctie:**
> **Example:**
> ```cpp
> class A {
> public:
> A() = default; // Expliciteert de default constructor
> A(const A&) = default; // Expliciteert de default copy constructor
> };
> ```
> In dit voorbeeld wordt de compiler gevraagd om de standaardimplementatie van de default constructor en de copy constructor te genereren voor klasse `A` [9](#page=9).
### 3.2 Deleted lidfuncties
Een deleted lidfunctie, aangegeven met `= delete;`, verbiedt de compiler expliciet de automatische generatie van die lidfunctie. Dit is het tegenovergestelde van een defaulted lidfunctie. Het wordt gebruikt om te voorkomen dat bepaalde operaties worden uitgevoerd, bijvoorbeeld om te verhinderen dat een object wordt gekopieerd of verplaatst, of om te zorgen voor expliciete controle over de levensduur van objecten [9](#page=9).
**Gevolgen van een deleted lidfunctie:**
Wanneer een lidfunctie als `delete` is gemarkeerd, zal elke poging om die functie aan te roepen resulteren in een compilatie-fout [9](#page=9).
**Voorbeeld van een deleted lidfunctie:**
> **Example:**
> ```cpp
> class A {
> public:
> A(const A&) = delete; // Verhindert kopiëren van objecten van klasse A
> };
> ```
> In dit geval zal de volgende code leiden tot een compilatie-fout:
> ```cpp
> A a;
> A b(a); // Compilatie-fout, omdat de copy constructor is gedelete
> ```
> Dit illustreert hoe `= delete;` effectief voorkomt dat een object wordt aangemaakt via de gedelete functie [9](#page=9).
---
# Move constructor en move operator
De move constructor en move operator in C++11 bieden een efficiënte manier om resources over te dragen in plaats van ze te kopiëren, wat cruciaal is voor prestatieoptimalisatie [12](#page=12) [14](#page=14).
### 4.1 Probleemstelling rond kopiëren
Standaard kopieerconstructeurs en toekenningsoperatoren kopiëren de attributen van een object. Dit kan inefficiënt zijn, vooral bij grote datastructuren, omdat het tijd en geheugen kost. Bij het kopiëren van objecten die resource-eigenaar zijn (zoals dynamisch toegewezen geheugen), is het standaardgedrag van de kopieerconstructeur en toekenningsoperator vaak onvoldoende, omdat dit leidt tot "gedeelde structuren" in plaats van onafhankelijke kopieën [11](#page=11) [15](#page=15) [17](#page=17).
> **Tip:** Een `std::vector` die aan een ander `std::vector` wordt toegewezen, voert een diepe kopie uit. Dit proces kan kostbaar zijn [11](#page=11).
### 4.2 De introductie van "move" semantiek
Sinds C++11 zijn de "move constructor" en de "move operator" geïntroduceerd. Hun doel is niet om attributen te kopiëren, maar om de attributen van het originele object over te nemen, vergelijkbaar met hoe `std::unique_ptr` werkt. Dit proces wordt ook wel "schaken" van attributen genoemd [12](#page=12).
#### 4.2.1 Gebruik van de move constructor en move operator
* **Move constructor:** Wordt gebruikt bij het initialiseren van een nieuw object met een bestaand object, met behulp van `std::move`.
```c++
A b(std::move(a));
```
* **Move operator:** Wordt gebruikt bij het toewijzen van de waarde van een bestaand object aan een ander bestaand object, met behulp van `std::move`.
```c++
c = std::move(b);
```
#### 4.2.2 Voorbeeldgebruik
In het volgende voorbeeld wordt `std::move` gebruikt bij `push_back` en de toekenningsoperator om de resources van `s1` over te dragen:
```c++
std::vector> v;
for (int i = 1; i <= 3; ++i) {
std::set s1, s2;
// ... iets toevoegen aan s1
v.push_back(std::move(s1)); // Move constructor van vector wordt aangeroepen
// ... opnieuw iets toevoegen aan s1
s2 = std::move(s1); // Move operator wordt aangeroepen
}
```
Na het gebruik van de move operator wordt het originele object (`s1` in dit geval) leeg achtergelaten. Sinds C++11 ondersteunen veel standaardcontainerfuncties, zoals `push_back`, move semantiek [13](#page=13).
### 4.3 Implementatie van move constructor en move operator
Standaard voorzien C++-compilers een move constructor en move operator. Deze standaardimplementaties passen de move constructor/operator toe op alle attributen. Het standaardgedrag voor primitieve types en raw pointers is echter kopiëren, niet verplaatsen [14](#page=14).
#### 4.3.1 Overerving voor raw pointers
Als een klasse een raw pointer als attribuut heeft, is het standaardgedrag van de copy/move constructor en operator niet optimaal, omdat dit kan leiden tot een gedeelde structuur. Om dit op te lossen, moet men deze zelf implementeren [15](#page=15) [17](#page=17):
* **Move constructor bij raw pointer:** Neem een ondiepe kopie van de pointer en zet de originele pointer op `nullptr`.
```c++
// Voorbeeld: Move constructor voor class A met raw pointer pA
A::A(A&& a) : vA(std::move(a.vA)), grA(a.grA), pA(a.pA) {
a.grA = 0;
a.pA = nullptr;
}
```
De move constructor is hierdoor eenvoudiger en efficiënter dan de copy constructor [16](#page=16).
* **Move operator bij raw pointer:** Neem de resources over, verwijder de eigen resources, en zet de originele pointer op `nullptr`.
```c++
// Voorbeeld: Move operator voor class A met raw pointer pA
A& A::operator=(A&& a) {
if (this != &a) {
vA = std::move(a.vA);
delete[] tA; // Verondersteld dat tA de eigen dynamische array is
grA = a.grA;
pA = a.pA;
a.grA = 0;
a.pA = nullptr;
}
return *this;
}
```
#### 4.3.2 Overerving voor primitieve types en andere objecten
* **Primitieve types:** Neem een kopie en zet eventueel de originele variabele op 0 [14](#page=14).
* **Andere objecten:** Gebruik de corresponderende move constructor/operator van het attribuut zelf [14](#page=14).
> **Tip:** De move operator wordt automatisch aangeroepen als het rechterlid een 'à la minute' aangemaakt object is (r-value) [19](#page=19).
### 4.4 De "Rule of Five"
Vóór C++11 kende men de "Rule of Three": als een klasse een aangepaste destructor, kopieerconstructeur of kopieer toekenningsoperator vereiste, vereiste deze vrijwel zeker alle drie [21](#page=21).
Sinds C++11 is dit uitgebreid naar de "Rule of Five": als je een van de standaardoperaties (destructor, kopieerconstructeur, kopieer toekenningsoperator, move constructor, move toekenningsoperator) definieert of `delete`, dan moet je ze allemaal definiëren of `delete`. Dit is om consistentie en correct resource management te garanderen [20](#page=20) [21](#page=21).
---
## 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 |
|------|------------|
| Automatische type-afleiding (auto) | In C++11 introduceert het `auto` sleutelwoord de mogelijkheid om het type van een variabele automatisch te laten afleiden op basis van de initialisatiewaarde, wat vooral handig is bij complexe datatypes. |
| Initialisatiesyntax | De manier waarop variabelen en objecten een beginwaarde krijgen bij hun declaratie. C++11 introduceerde uniforme notatie met accolades voor een consistentere aanpak. |
| Uniforme initialisatiesyntax | Een methode geïntroduceerd in C++11 die accolades `{}` gebruikt voor de initialisatie van diverse datatypen, inclusief klassen, arrays en standaardcontainers, wat zorgt voor een eenduidiger code. |
| In-klasse initialisatie | De mogelijkheid om attributen van een klasse direct bij hun declaratie binnen de klassedefinitie te initialiseren, een functionaliteit die is toegevoegd sinds C++11. |
| Defaulted lidfunctie | Een speciaal aangegeven lidfunctie (zoals een constructor of operator) die de compiler expliciet instrueert om de standaard door de compiler gegenereerde implementatie te gebruiken. |
| Deleted lidfunctie | Een lidfunctie die expliciet is gemarkeerd als ` = delete`, wat de compiler verbiedt om deze functie te genereren of te gebruiken, om ongewenst gedrag te voorkomen. |
| Copy constructor | Een constructor die een nieuw object initialiseert met de waarden van een reeds bestaand object van hetzelfde type, doorgaans door diepe kopieën van de attributen te maken. |
| Move constructor | Een constructor die de bronnen van een ander object (het *rvalue* object) overneemt in plaats van ze te kopiëren, waardoor een efficiëntere overdracht van resources mogelijk wordt en het originele object vaak in een lege of verplaatste staat achterblijft. |
| Toekenningsoperator (operator=) | Een operator die wordt gebruikt om de waarde van een reeds bestaand object toe te kennen aan een ander object van hetzelfde type, waarbij de bestaande inhoud van het doelobject doorgaans wordt verwijderd of beheerd. |
| Move operator | Een operator die de bronnen van een ander object (het *rvalue* object) overneemt voor toekenning aan een bestaand object, vergelijkbaar met de move constructor, maar dan voor toekenning. |
| The Big Three | Verwijst naar de drie speciale lidfuncties (destructor, copy constructor, copy assignment operator) die historisch gezien vaak tegelijkertijd door de programmeur moesten worden gedefinieerd om correct geheugenbeheer te garanderen vóór C++11. |
| The Big Five | Sinds C++11 wordt dit concept uitgebreid met de move constructor en move operator, wat resulteert in vijf speciale lidfuncties (destructor, copy constructor, copy assignment operator, move constructor, move operator) die symmetrisch gedefinieerd of gedelete moeten worden. |
| Rule of Three | Een programmeerrichtlijn die stelt dat als een klasse een aangepaste destructor, copy constructor of copy assignment operator vereist, deze waarschijnlijk alle drie nodig heeft om correcte resource management te waarborgen. |
| Rule of Five | Een uitbreiding van de "Rule of Three" in C++11, die aangeeft dat als een van de vijf speciale lidfuncties (destructor, copy constructor, copy assignment operator, move constructor, move assignment operator) wordt gedefinieerd of gedelete, de andere vier ook expliciet moeten worden gedefinieerd of gedelete. |
| Rule of Zero | Een modern programmeerprincipe in C++ dat adviseert om aangepaste destructors, copy/move constructors of assignment operators te vermijden door gebruik te maken van klassen die resource management correct afhandelen (bv. RAII). Klassen die dit niet doen, zouden geen aangepaste speciale lidfuncties moeten hebben. |
| Rvalue | Een tijdelijk object dat niet direct kan worden toegewezen aan een lvalue. Move semantiek maakt gebruik van rvalues om efficiënte overdracht van resources mogelijk te maken. |
| Lvalue | Een expressie die verwijst naar een geheugenlocatie die kan worden geïdentificeerd, zoals een variabele. Lvalues kunnen worden toegewezen aan nieuwe waarden. |