8 Generics

8.1 Templates

In C++ bezeichnet man “Generics” üblicherweise als Vorlagen oder auf Englisch “Templates”. Templates ermöglichen es, generischen Code zu schreiben, der mit unterschiedlichen Datentypen funktioniert, ohne dass der Code für jeden einzelnen Typ neu geschrieben werden muss.

8.1.1 Klassenvorlagen

Ein einfaches Beispiel für eine generische Klasse wäre eine Klasse für ein Paar von Werten:

template <typename T>
class Pair {
private:
    T first;
    T second;
public:
    Pair(T a, T b) : first(a), second(b) {}
    T getFirst() { return first; }
    T getSecond() { return second; }
};

// Verwendung:
Pair<int> intPair(1, 2);
Pair<std::string> stringPair("Hello", "World");

8.1.2 Funktionsvorlagen

Man kann auch generische Funktionen erstellen:

template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

// Verwendung:
int a = getMax(3, 4);  // gibt 4 zurück
double b = getMax(3.5, 4.5);  // gibt 4.5 zurück

8.1.3 Mehrere Template-Parameter

Man kann mehrere Template-Parameter haben:

template <typename T, typename U>
class Pair {
private:
    T first;
    U second;
public:
    Pair(T a, U b) : first(a), second(b) {}
    T getFirst() { return first; }
    U getSecond() { return second; }
};

Pair<int, std::string> mixedPair(1, "One");

8.1.4 Template-Spezialisierung

Man kann auch spezialisierte Implementierungen für bestimmte Typen bereitstellen:

template <>
class Pair<bool> {
    // Spezialisierte Implementierung für den Typ bool
};

8.1.5 Grenzen und Herausforderungen

  1. Kompilierungsfehler: Fehler in Template-Code können schwer zu verstehen sein und zu langen Fehlermeldungen führen.

  2. Code-Aufblähung: Jede Instanziierung eines Templates kann eine separate Kopie des Codes erzeugen. Compiler verwenden jedoch oft Techniken wie “Template-Instantiation”, um dies zu minimieren.

  3. Funktions- und Klassen-Templates sind nicht immer austauschbar: Manchmal ist es nicht möglich, einen generischen Code mit Funktions-Templates zu schreiben, der mit Klassenvorlagen funktioniert, und umgekehrt.

8.1.6 TLDR;

Templates in C++ bieten eine mächtige Möglichkeit, generischen Code zu schreiben, der mit einer Vielzahl von Datentypen arbeiten kann. Sie sind das Rückgrat von vielen C++-Standardbibliothekskomponenten, wie den Containerklassen und Algorithmen. Trotz ihrer Komplexität ermöglichen sie eine hohe Wiederverwendbarkeit und Typsicherheit.

8.2 Lambda-Funktionen

Lambda-Funktionen, häufig einfach als Lambdas bezeichnet, sind anonyme Funktionen, die direkt innerhalb des Körpers einer umgebenden Funktion definiert werden können. Sie wurden mit C++11 eingeführt und ermöglichen eine kurze und ausdrucksstarke Art, Funktionsobjekte zu schreiben.

8.2.1 Grundlegende Syntax

Eine Lambda-Funktion in C++ hat die folgende allgemeine Form:

[capture](parameterliste) -> Rückgabetyp { Funktionskörper }

Beispiele:

auto lambda1 = []() { std::cout << "Hello, World!" << std::endl; };
lambda1();  // Ausgabe: Hello, World!

auto lambda2 = [](int x, int y) -> int { return x + y; };
int sum = lambda2(3, 4);  // sum hat den Wert 7

8.2.2 Capture-Klausel

Mit der Capture-Klausel können Sie Variablen aus dem umgebenden Gültigkeitsbereich “einfangen” und in der Lambda verwenden:

Beispiel:

int a = 5, b = 7;
auto lambda3 = [a, &b]() { std::cout << a << " " << ++b << std::endl; };
lambda3();  // Ausgabe: 5 8

8.2.3 Fortgeschrittene Verwendung

  1. Mehrzeilige Lambdas: Die Körperfunktion kann mehrere Anweisungen umfassen.

    auto printDetails = [](std::string name, int age) {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    };
    printDetails("Alice", 30);
  2. Rückgabewerttyp-Inferezenz: Wenn der Rückgabetyp nicht explizit angegeben ist, wird er vom Compiler basierend auf dem Rückgabewert der Lambda abgeleitet.

    auto lambda4 = [](int x, int y) { return x + y; };
  3. Generische Lambdas (ab C++14): Mit automatischer Typdeduktion können Sie generische Lambdas schreiben.

    auto genericLambda = [](auto x, auto y) { return x + y; };
  4. Lambdas mit Zustand (ab C++14): Mit der Verwendung von mutable können Lambdas, die per Wert einfangen, Zustände ändern.

    int z = 0;
    auto increment = [z]() mutable { return ++z; };

8.2.4 Verwendungszwecke

Lambdas sind besonders nützlich in Verbindung mit Algorithmen aus der C++ Standardbibliothek, da sie eine kompakte Möglichkeit bieten, benutzerdefinierte Operationen direkt an Ort und Stelle zu definieren.

Beispiel:

std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int n) { std::cout << n * n << " "; });  // Ausgabe der Quadrate

8.2.5 TLDR

Lambda-Funktionen in C++ sind eine mächtige Ergänzung zur Sprache und ermöglichen eine kurze, ausdrucksstarke und funktionale Programmierweise. Es ist wichtig, die Capture-Klausel und den Lebenszyklus der eingefangenen Variablen zu verstehen, um häufige Fehler zu vermeiden.