Introduction
C++ est un langage de programmation puissant et flexible qui donne au développeur un contrôle énorme sur les ressources système. Cependant, cette liberté s'accompagne d'une grande responsabilité. Les programmeurs débutants commettent souvent les mêmes erreurs qui peuvent conduire à des bugs difficiles à trouver, des fuites mémoire et un comportement indéfini. Dans cet article, nous allons examiner les principales erreurs des développeurs C++ débutants et montrer comment les éviter.
1. Fuites mémoire et gestion incorrecte des ressources
Problème : gestion manuelle de la mémoire via new/delete
L'une des erreurs les plus courantes est d'oublier d'appeler delete pour un objet créé via new. Cela entraîne une fuite mémoire.
void example() { int* ptr = new int(42); // ... du code ... // Oubli de delete ptr;}Dans des cas plus complexes, si une exception se produit entre new et delete, la mémoire n'est pas non plus libérée.
Solution : utiliser RAII et les pointeurs intelligents
Les normes C++11 et ultérieures fournissent des pointeurs intelligents qui gèrent automatiquement la durée de vie d'un objet.
#include <memory>
void example() { std::unique_ptr<int> ptr = std::make_unique<int>(42); // La mémoire sera automatiquement libérée en quittant la portée}Pour les tableaux, utilisez std::vector au lieu de la gestion manuelle :
// Mauvais :int* arr = new int[100];// ... travail ...delete[] arr;
// Bon :std::vector<int> arr(100);// La mémoire est gérée automatiquement2. Comportement indéfini
Problème : accès à un indice de tableau hors limites
C++ n'a pas de vérification intégrée des limites pour les tableaux bruts. Accéder à un élément en dehors du tableau est un comportement indéfini classique.
int arr[5] = {1, 2, 3, 4, 5};arr[10] = 42; // Comportement indéfini ! Peut "fonctionner", peut planterSolution : utiliser std::array et std::vector avec at()
#include <vector>#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};// arr.at(10) lancera une exception std::out_of_range
std::vector<int> vec = {1, 2, 3};vec.at(5); // Vérification sécurisée des limitesProblème : utilisation de variables non initialisées
int x;std::cout << x; // Comportement indéfini : x n'est pas initialiséInitialisez toujours les variables :
int x = 0;int* ptr = nullptr;3. Confusion entre pointeurs et références
Problème : retour d'un pointeur vers un objet local
int* getBadPointer() { int local = 42; return &local; // Erreur ! local est détruit en quittant la fonction}Cela conduit à un pointeur pendant. Utiliser un tel pointeur est un comportement indéfini.
Solution : retourner par valeur ou utiliser des pointeurs intelligents
// Retour par valeur (préféré pour les types simples)int getValue() { return 42;}
// Retourner un pointeur intelligent pour les objets dynamiquesstd::unique_ptr<MyClass> createObject() { return std::make_unique<MyClass>();}Problème : confusion entre références et pointeurs
Les références ne peuvent pas être nullptr et ne peuvent pas être réaffectées. Les pointeurs le peuvent. Les débutants essaient souvent de passer nullptr à une fonction qui attend une référence.
void process(int& ref) { // ref est garantie de référencer un objet existant}
int* ptr = nullptr;// process(*ptr); // Erreur ! Déférencer nullptr est un UBUtilisez des références lorsque l'objet est requis, et des pointeurs (ou std::optional) lorsqu'il peut être null.