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?
