Le C++ moderne a connu une série de mises à jour importantes depuis C++11, chacune apportant de nouvelles fonctionnalités et améliorations destinées à rendre le langage plus efficace, plus lisible et plus facile à maintenir. Voici un bref aperçu des principales fonctionnalités introduites dans chaque version depuis C++11, accompagné d'un commentaire sur leur usage :
C++11
C++11 a marqué une évolution majeure du langage C++, en introduisant plusieurs fonctionnalités puissantes qui ont modernisé et simplifié la programmation C++. En voici quelques-unes des plus marquantes, avec des exemples pour illustrer leur usage :
1. Déduction de type avec auto
L'attribut auto permet au compilateur de déduire automatiquement le type d'une variable à partir de son initialiseur.
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"; // const char*
2. Expressions lambda
Les lambdas offrent un moyen concis de définir des fonctions anonymes directement dans votre code.
auto add = [](int a, int b) { return a + b; };
int result = add(3, 4); // result is 7
3. Boucles for basées sur des intervalles
Elles simplifient l'itération sur des collections telles que les tableaux, les vecteurs et les autres conteneurs.
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int n : numbers) {
std::cout << n << " ";
}
4. Pointeurs intelligents
Ils introduisent std::unique_ptr et std::shared_ptr pour gérer automatiquement la mémoire dynamique et éviter les fuites mémoire.
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. Sémantique de déplacement
La sémantique de déplacement optimise les performances en permettant de transférer les ressources plutôt que de les copier.
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
Remplace NULL par nullptr pour représenter les pointeurs nuls de manière plus sûre.
int* p = nullptr; // p is a null pointer
7. constexpr
Permet de définir des expressions constantes évaluées à la compilation, ce qui améliore les performances en effectuant les calculs dès la compilation.
constexpr int square(int x) {
return x * x;
}
int arr[square(5)]; // Creates an array of size 25
8. std::thread
Fournit un moyen standard de créer et de gérer des threads, ouvrant la voie à la programmation concurrente.
#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. Templates variadiques
Ils prennent en charge les templates comportant un nombre variable d'arguments, ce qui rend la programmation par templates plus souple.
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // Fold expression (C++17)
}
print(1, 2, "three", 4.0);
10. Initialisation uniforme
Elle offre une syntaxe cohérente pour initialiser variables et conteneurs.
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 s'est appuyé sur les fondations posées par C++11 en introduisant plusieurs améliorations importantes pour la lisibilité, la souplesse et les performances du code. Voici les principales fonctionnalités avec des exemples :
1. Lambdas génériques
C++14 autorise l'emploi d' auto dans les listes de paramètres des lambdas, ce qui les rend plus souples et plus simples à utiliser avec du code générique.
Exemple :
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. Templates de variables
Les templates de variables permettent de définir des templates pour des variables, et plus seulement pour des fonctions ou des classes.
Exemple :
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. Déduction du type de retour
C++14 permet aux fonctions de déduire automatiquement leur type de retour grâce à auto.
Exemple :
auto add(int a, int b) {
return a + b;
}
std::cout << add(1, 2) << std::endl; // Output: 3
4. std::make_unique
Cette fonction utilitaire simplifie la création d'instances std::unique_ptr, ce qui rend le code plus lisible et moins sujet aux erreurs.
Exemple :
#include <memory>
auto ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl; // Output: 42
5. Attribut deprecated
L'attribut [[deprecated]] permet de marquer des fonctions et des variables comme obsolètes, en générant des avertissements à la compilation lorsqu'elles sont utilisées.
Exemple :
[[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. Littéraux binaires et séparateurs de chiffres
C++14 a introduit les littéraux binaires préfixés par 0b ou 0B ainsi que l'apostrophe comme séparateur de chiffres pour améliorer la lisibilité des grands nombres.
Exemple :
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 a apporté une foule de nouvelles fonctionnalités et d'améliorations, rendant le langage plus expressif et plus convivial. En voici quelques-unes des principales, avec des explications détaillées et des exemples :
1. std::optional
std::optional est un utilitaire qui représente une valeur pouvant être présente ou non. Il est utile pour les fonctions susceptibles de ne pas renvoyer de valeur.
Exemple :
#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 est une union à typage sûr, permettant à une variable de contenir l'un de plusieurs types spécifiés.
Exemple :
#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 est un conteneur à typage sûr pour une valeur unique de n'importe quel type. Il est utile lorsque le type de la valeur n'est pas connu à la compilation.
Exemple :
#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. Liaisons structurées
Les liaisons structurées (structured bindings) permettent de décomposer directement des objets de type tuple en variables individuelles.
Exemple :
#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 permet une compilation conditionnelle à la compilation.
Exemple :
#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. Expressions de pliage
Les expressions de pliage (fold expressions) simplifient l'usage des templates variadiques en offrant une syntaxe concise pour les opérations sur les paquets de paramètres.
Exemple :
#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. Bibliothèque filesystem
L'attribut <filesystem> fournit les outils nécessaires pour effectuer des opérations sur les systèmes de fichiers et leurs composants.
Exemple :
#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";
}
}
L'ensemble de ces fonctionnalités renforce la puissance et la souplesse du C++, facilitant l'écriture d'un code robuste, moderne et efficace.
C++20
C++20 constitue une étape majeure dans l'évolution du C++, avec une série de fonctionnalités puissantes qui renforcent l'expressivité, la sûreté et les performances du langage. Voici un aperçu détaillé des principales nouveautés, avec des exemples :
1. Concepts
Les concepts permettent de spécifier des contraintes sur les paramètres de template, ce qui rend les templates plus lisibles et les messages d'erreur plus compréhensibles.
#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
La bibliothèque Ranges introduit une nouvelle manière de travailler avec des séquences de données, rendant le code plus lisible et plus expressif.
#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
Les coroutines permettent d'écrire du code asynchrone dans un style séquentiel, ce qui simplifie le développement d'applications concurrentes.
#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
Les modules offrent une nouvelle façon d'organiser et d'importer le code, ce qui améliore les temps de compilation et l'encapsulation.
// 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. Comparaison à trois voies (opérateur spaceship)
L'opérateur de comparaison à trois voies (<=>) simplifie l'implémentation des comparaisons et offre une manière cohérente de les gérer.
#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. Bibliothèque de calendriers et de fuseaux horaires
Cette bibliothèque offre une prise en charge complète de la manipulation des dates, des heures et des fuseaux horaires.
#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 enrichit considérablement le langage, le rendant plus moderne et plus puissant pour un large éventail de tâches de programmation. L'ensemble de ces fonctionnalités améliore la robustesse, la lisibilité et la maintenabilité du code C++.
C++23
C++23 introduit plusieurs fonctionnalités puissantes qui renforcent les capacités, la sûreté et la facilité d'utilisation du langage. Voici un aperçu détaillé des ajouts les plus significatifs :
1. std::expected
std::expected est un nouvel utilitaire de gestion des erreurs, similaire à std::optional, mais avec une gestion intégrée de l'état d'erreur.
Exemple:
#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 est une vue de tableau multidimensionnel, offrant un moyen de décrire la forme et la disposition des données en mémoire.
Exemple:
#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. Réflexion (expérimentale)
C++23 introduit des capacités expérimentales de réflexion, permettant l'introspection des types et de leurs propriétés à la compilation.
Exemple:
#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
L'attribut if consteval permet au code de vérifier s'il est évalué à la compilation.
Exemple:
#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. Attribut [[assume]]
L'attribut [[assume]] permet aux développeurs de fournir des hypothèses à l'optimiseur, améliorant ainsi les performances en informant le compilateur des invariants.
Exemple:
#include <iostream>
int main() {
int x = 5;
[[assume(x > 0)]];
std::cout << "x is positive.\n";
}
Ces fonctionnalités ont considérablement renforcé l'expressivité, la sûreté et les performances du C++, qui demeure un choix solide pour le développement logiciel moderne. Mais combien d'entre elles avez-vous déjà utilisées ?
