Blog 10 Min. Lesezeit

C++26 steht vor der Tür – doch welche wichtigen Funktionen wurden seit C++11 zu C++ hinzugefügt?

Artikel teilen
C++26 is coming, but what are the major features that have been added to C++ since C++11?

Modernes C++ hat seit C++11 eine Reihe bedeutender Weiterentwicklungen erfahren. Jede Version brachte neue Funktionen und Verbesserungen mit dem Ziel, die Sprache effizienter, lesbarer und wartbarer zu machen. Hier ist ein kompakter Überblick über die wichtigsten Neuerungen der einzelnen Versionen seit C++11 – jeweils mit einer kurzen Einordnung ihrer Verwendung:

C++11

C++11 markierte einen entscheidenden Entwicklungsschritt der Sprache und führte zahlreiche leistungsfähige Funktionen ein, die die C++-Programmierung modernisierten und vereinfachten. Hier sind einige der wichtigsten Neuerungen mit Beispielen für ihren Einsatz:

1. Automatische Typableitung mit auto

Das auto Das Schlüsselwort ermöglicht dem Compiler, den Typ einer Variablen automatisch aus ihrem Initialisierer abzuleiten.

auto x = 42;       // int
auto y = 3.14;     // double
auto s = "hello";  // const char*

2. Lambda-Ausdrücke

Lambdas bieten eine kompakte Möglichkeit, anonyme Funktionen direkt im Code zu definieren.

auto add = [](int a, int b) { return a + b; };
int result = add(3, 4);  // result is 7

3. Bereichsbasierte for-Schleifen

Sie vereinfachen die Iteration über Sammlungen wie Arrays, Vektoren und andere Container.

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int n : numbers) {
    std::cout << n << " ";
}

4. Smart Pointer

Introduces std::unique_ptr and std::shared_ptr zur automatischen Verwaltung dynamischen Speichers und helfen dadurch, Speicherlecks zu vermeiden.

std::unique_ptr<int> ptr(new int(10));
// No need to delete ptr manually; it will be deleted when it goes out of scope

5. Move-Semantik

Die Move-Semantik verbessert die Performance, indem Ressourcen übertragen statt kopiert werden können.

std::vector<int> makeVector() {
    std::vector<int> v = {1, 2, 3};
    return v;  // Moves v rather than copying
}

std::vector<int> v = makeVector();  // Efficient move

6. nullptr

Ersetzt NULL with nullptr zur sichereren Darstellung von Nullzeigern.

int* p = nullptr;  // p is a null pointer

7. constexpr

Ermöglicht konstante Ausdrücke zur Compile-Zeit und kann die Performance verbessern, indem Berechnungen bereits beim Kompilieren ausgeführt werden.

constexpr int square(int x) {
    return x * x;
}

int arr[square(5)];  // Creates an array of size 25

8. std::thread

Bietet eine standardisierte Möglichkeit, Threads zu erstellen und zu verwalten, und ermöglicht damit nebenläufige Programmierung.

#include <thread>
#include <iostream>

void hello() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(hello);
    t.join();  // Wait for thread to finish
    return 0;
}

9. Variadische Templates

Unterstützen Templates mit einer variablen Anzahl von Argumenten und machen die Template-Programmierung dadurch flexibler.

template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << std::endl;  // Fold expression (C++17)
}

print(1, 2, "three", 4.0);

10. Einheitliche Initialisierung

Ermöglicht eine einheitliche Syntax zur Initialisierung von Variablen und Containern.

int arr[] = {1, 2, 3};
std::vector<int> v = {1, 2, 3, 4, 5};
struct Point { int x, y; };
Point p = {1, 2};

C++14

C++14 baute auf C++11 auf und führte wichtige Verbesserungen für bessere Lesbarkeit, Flexibilität und Performance ein. Hier sind die zentralen Neuerungen mit Beispielen:

1. Generic Lambdas

C++14 erlaubt die Verwendung von auto in Lambda-Parameterlisten. Dadurch werden Lambdas flexibler und lassen sich einfacher mit generischem Code verwenden.

Beispiel:

auto add = [](auto a, auto b) { return a + b; };
std::cout << add(1, 2) << std::endl; // Output: 3
std::cout << add(1.5, 2.3) << std::endl; // Output: 3.8

2. Variable Templates

Variablen-Templates ermöglichen Templates auch für Variablen und nicht nur für Funktionen oder Klassen.

Beispiel:

template<typename T>
constexpr T pi = T(3.1415926535897932385L);

std::cout << pi<double> << std::endl; // Output: 3.14159
std::cout << pi<float> << std::endl; // Output: 3.14159f

3. Return Type Deduction

C++14 ermöglicht Funktionen, ihren Rückgabetyp mithilfe von auto.

Beispiel:

auto add(int a, int b) {
    return a + b;
}

std::cout << add(1, 2) << std::endl; // Output: 3

4. std::make_unique

Diese Hilfsfunktion vereinfacht die Erstellung von std::unique_ptr -Instanzen und macht den Code dadurch lesbarer und weniger fehleranfällig.

Beispiel:

#include <memory>

auto ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl; // Output: 42

5. Deprecated Attribute

Das [[deprecated]] -Attribut kann Funktionen und Variablen als veraltet kennzeichnen. Bei ihrer Verwendung erzeugt der Compiler entsprechende Warnungen.

Beispiel:

[[deprecated("Use new_function() instead")]]
void old_function() {}

void new_function() {}

int main() {
    old_function(); // Warning: 'old_function' is deprecated: Use new_function() instead
    new_function();
}

6. Binary Literals and Digit Separators

C++14 führte Binärliterale mit dem Präfix 0b or 0B sowie das einfache Anführungszeichen als Zifferntrenner ein, um große Zahlen besser lesbar zu machen.

Beispiel:

int binary = 0b1010; // Binary literal
int largeNumber = 1'000'000; // Digit separator

std::cout << binary << std::endl; // Output: 10
std::cout << largeNumber << std::endl; // Output: 1000000

C++17

C++17 brachte zahlreiche neue Funktionen und Verbesserungen, die die Sprache ausdrucksstärker und benutzerfreundlicher machten. Hier sind einige der wichtigsten Ergänzungen mit Erläuterungen und Beispielen:

1. std::optional

std::optional ist ein Hilfstyp für einen Wert, der vorhanden sein kann oder auch nicht. Er eignet sich besonders für Funktionen, die möglicherweise keinen Wert zurückgeben.

Beispiel:

#include <optional>
#include <iostream>

std::optional<int> find_even_number(int num) {
    if (num % 2 == 0) return num;
    return std::nullopt;
}

int main() {
    auto result = find_even_number(4);
    if (result) {
        std::cout << "Even number: " << *result << "\n";
    } else {
        std::cout << "Not an even number\n";
    }
}

2. std::variant

std::variant ist eine typsichere Union, mit der eine Variable einen von mehreren festgelegten Typen enthalten kann.

Beispiel:

#include <variant>
#include <iostream>

int main() {
    std::variant<int, float, std::string> my_variant;
    my_variant = 10;
    std::cout << std::get<int>(my_variant) << "\n";

    my_variant = 3.14f;
    std::cout << std::get<float>(my_variant) << "\n";

    my_variant = "Hello";
    std::cout << std::get<std::string>(my_variant) << "\n";
}

3. std::any

std::any ist ein typsicherer Container für einzelne Werte beliebigen Typs. Er ist nützlich, wenn der Typ eines Werts zur Compile-Zeit nicht bekannt ist.

Beispiel:

#include <any>
#include <iostream>

int main() {
    std::any my_any;
    my_any = 42;
    std::cout << std::any_cast<int>(my_any) << "\n";

    my_any = std::string("Hello");
    std::cout << std::any_cast<std::string>(my_any) << "\n";
}

4. Structured Bindings

Strukturierte Bindungen ermöglichen es, tupelähnliche Objekte direkt in einzelne Variablen zu zerlegen.

Beispiel:

#include <tuple>
#include <iostream>

std::tuple<int, float, std::string> get_data() {
    return {1, 2.3f, "test"};
}

int main() {
    auto [i, f, s] = get_data();
    std::cout << "i: " << i << ", f: " << f << ", s: " << s << "\n";
}

5.if constexpr

if constexpr ermöglicht bedingte Entscheidungen zur Compile-Zeit.

Beispiel:

#include <iostream>

template <typename T>
void print_type_info(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integral type\n";
    } else {
        std::cout << "Non-integral type\n";
    }
}

int main() {
    print_type_info(42);
    print_type_info(3.14);
}

6. Fold Expressions

Fold Expressions vereinfachen variadische Templates durch eine kompakte Syntax für Operationen auf Parameter-Packs.

Beispiel:

#include <iostream>

template<typename... Args>
auto sum(Args... args) {
    return (... + args); // fold expression
}

int main() {
    std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << "\n";
}

7. Filesystem Library

Das <filesystem> -Bibliothek stellt Funktionen für Operationen auf Dateisystemen und deren Komponenten bereit.

Beispiel:

#include <filesystem>
#include <iostream>

int main() {
    std::filesystem::path p{"example.txt"};
    if (std::filesystem::exists(p)) {
        std::cout << p << " exists\n";
    } else {
        std::cout << p << " does not exist\n";
    }
}

Zusammen erhöhen diese Funktionen die Leistungsfähigkeit und Flexibilität von C++ und erleichtern das Schreiben von robustem, modernem und effizientem Code.

C++20

C++20 ist ein Meilenstein in der Entwicklung von C++. Die Version brachte zahlreiche leistungsfähige Funktionen, die Ausdrucksstärke, Sicherheit und Performance der Sprache verbessern. Hier ein genauerer Blick auf die wichtigsten Neuerungen mit Beispielen:

1. Concepts

Concepts ermöglichen es, Anforderungen an Template-Parameter ausdrücklich zu formulieren. Dadurch werden Templates lesbarer und Fehlermeldungen verständlicher.

#include <concepts>
#include <iostream>

template <typename T>
concept Integral = std::is_integral_v<T>;

template <Integral T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add(3, 4) << '\n';  // Works
    // std::cout << add(3.0, 4.0) << '\n';  // Error: double doesn't satisfy Integral
}

2. Ranges

Die Ranges-Bibliothek führt eine neue Art der Arbeit mit Datensequenzen ein und macht Code dadurch lesbarer und ausdrucksstärker.

#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    auto result = v | std::ranges::views::filter([](int n) { return n % 2 == 0; });

    for (int n : result) {
        std::cout << n << ' ';  // Output: 2 4
    }
}

3. Coroutines

Koroutinen ermöglichen asynchronen Code in einem sequenziell wirkenden Programmierstil und vereinfachen so die Entwicklung nebenläufiger Anwendungen.

#include <coroutine>
#include <iostream>

struct ReturnObject {
    struct promise_type {
        ReturnObject get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        void return_void() {}
    };
};

ReturnObject foo() {
    std::cout << "Hello ";
    co_await std::suspend_always{};
    std::cout << "World\n";
}

int main() {
    auto handle = foo();
    handle.resume();
    handle.resume();
}

4. Modules

Module bieten eine neue Möglichkeit, Code zu organisieren und zu importieren. Sie können Kompilierzeiten verbessern und die Kapselung des Codes stärken.

// my_module.ixx
export module my_module;
export int add(int a, int b) {
    return a + b;
}

// main.cpp
import my_module;
#include <iostream>

int main() {
    std::cout << add(3, 4) << '\n';  // Output: 7
}

5. Three-way Comparison (Spaceship Operator)

Der Drei-Wege-Vergleichsoperator (<=>) vereinfacht die Implementierung von Vergleichen und bietet dafür einen einheitlichen Ansatz.

#include <compare>
#include <iostream>

struct Point {
    int x, y;
    auto operator<=>(const Point&) const = default;
};

int main() {
    Point p1{1, 2}, p2{2, 3};
    if (p1 < p2) {
        std::cout << "p1 is less than p2\n";  // Output
    }
}

6. Calendar and Time Zone Library

Diese Bibliothek bietet umfassende Unterstützung für den Umgang mit Datum, Uhrzeit und Zeitzonen.

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    auto now = system_clock::now();
    auto today = floor<days>(now);
    std::cout << "Today is: " << today.time_since_epoch().count() << " days since epoch\n";
}

C++20 erweitert die Sprache erheblich und macht sie für ein breites Spektrum an Programmieraufgaben moderner und leistungsfähiger. Zusammen verbessern diese Funktionen Robustheit, Lesbarkeit und Wartbarkeit von C++-Code.

C++23

C++23 führt weitere leistungsfähige Funktionen ein, die Möglichkeiten, Sicherheit und Benutzerfreundlichkeit der Sprache verbessern. Hier ein genauerer Blick auf einige der wichtigsten Ergänzungen:

1. std::expected

std::expected ist ein neuer Hilfstyp für die Fehlerbehandlung, ähnlich wie std::optional jedoch mit integrierter Verwaltung eines Fehlerzustands.

Beispiel:

#include <iostream>
#include <expected>

std::expected<int, std::string> divide(int a, int b) {
    if (b == 0) {
        return std::unexpected("Division by zero!");
    }
    return a / b;
}

int main() {
    auto result = divide(10, 0);
    if (!result) {
        std::cout << "Error: " << result.error() << '\n';
    } else {
        std::cout << "Result: " << *result << '\n';
    }
}

2. std::mdspan

std::mdspan ist eine mehrdimensionale Array-Ansicht, mit der Form und Speicherlayout von Daten beschrieben werden können.

Beispiel:

#include <iostream>
#include <mdspan>

int main() {
    int data[6] = {1, 2, 3, 4, 5, 6};
    std::mdspan<int, std::extents<2, 3>> mdspan(data);
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 3; ++j) {
            std::cout << mdspan(i, j) << ' ';
        }
        std::cout << '\n';
    }
}

3. Reflection (Experimental)

C++23 führt experimentelle Reflection-Funktionen ein, die eine Introspektion von Typen und ihren Eigenschaften zur Compile-Zeit ermöglichen.

Beispiel:

#include <iostream>
#include <experimental/reflect>

struct MyStruct {
    int a;
    double b;
    void foo() {}
};

int main() {
    auto type = reflexpr(MyStruct);
    for (auto member : type.get_data_members()) {
        std::cout << "Member name: " << member.get_name() << '\n';
    }
}

4. if consteval

Das if consteval -Statement ermöglicht es dem Code zu prüfen, ob er gerade zur Compile-Zeit ausgewertet wird.

Beispiel:

#include <iostream>

constexpr int factorial(int n) {
    if consteval {
        if (n < 0) throw "Negative input!";
    }
    return n <= 1 ? 1 : n * factorial(n - 1);
}

int main() {
    std::cout << factorial(5) << '\n'; // OK
    // std::cout << factorial(-1) << '\n'; // Compile-time error
}

5. [[assume]]-Attribut

Das [[assume]] -Attribut ermöglicht Entwicklern, dem Optimierer Annahmen mitzuteilen. Indem der Compiler über Invarianten informiert wird, können zusätzliche Optimierungen möglich werden.

Beispiel:

#include <iostream>

int main() {
    int x = 5;
    [[assume(x > 0)]];
    std::cout << "x is positive.\n";
}

Diese Funktionen haben Ausdrucksstärke, Sicherheit und Performance von C++ deutlich verbessert und tragen dazu bei, dass die Sprache eine robuste Wahl für moderne Softwareentwicklung bleibt. Doch wie viele davon haben Sie bereits eingesetzt?

Artikel teilen