Mémoire dynamique en C++
Les programmes précédents utilisaient des objets avec des durées de vie prédéfinies :
- Objets globaux : alloués au lancement, détruits à l'arrêt
- Objets locaux automatiques : créés à l'entrée du bloc, détruits à la sortie
- Objets statiques locaux : alloués avant première utilisation, détruits à l'arrêt
Pour dépasser ces limitations, C++ permet l'allocation dynamique d'objets dont la durée de vie est indépendante de leur scope. Leur destruction nécessite une libération explicite.
La libération correcte est une source majeure d'erreurs. La bibliothèque standard fournit donc des pointeurs intelligents qui garantissent une libération automatique.
Pointeurs intelligents
Deux types principaux existent :
shared_ptr: permet le partage de la propriétéunique_ptr: propriété exclusive
Définis dans l'en-tête <memory>, ils se comportent comme des pointeurs classiques mais gèrent automatiquement la libération.
shared_ptr
Création recommandée avec make_shared :
auto ptrDouble = make_shared<double>(3.14);
auto ptrChaine = make_shared<string>("Bonjour");
auto ptrVide = make_shared<vector<int>>();
Le comptage de références gère automatiquement la durée de vie :
auto principal = make_shared<int>(100);
auto secondaire(principal); // Compteur = 2
principal.reset(); // Compteur = 1
// L'objet persiste tant que secondaire existe
Gestion directe de mémoire
Les opérateurs new et delete sont risqués :
double* nouveauDouble = new double(2.718);
// ... utilisation ...
delete nouveauDouble; // Libération manuelle obligatoire
Problèmes fréquents :
- Oubli de
delete→ fuite mémoire - Utilisation après libération → comportement indéfini
- Double libération → corruption mémoire
Combinaison avec pointeurs bruts
Initialisation possible avec new :
shared_ptr<complexe> ptrComplexe(new complexe(1, 2));
Pièges à éviter :
- Ne pas utiliser le même pointeur brut pour plusieurs smart pointers
- Ne pas appeler
deletesur un pointeur obtenu parget() - Ne pas initialiser un smart pointer avec
get()
Gession de ressources non-mémoire
Les pointeurs intelligents peuvent gérer d'autres ressources :
struct ConnexionBDD;
ConnexionBDD* ouvrirConnexion();
void fermerConnexion(ConnexionBDD*);
void utiliserBDD() {
ConnexionBDD* bdd = ouvrirConnexion();
shared_ptr<ConnexionBDD> gestionnaire(bdd, fermerConnexion);
// La connexion se fermera automatiquement
}
unique_ptr
Pointeur à propriété exclusive :
unique_ptr<vector<int>> ptrVec(new vector<int>{1,2,3});
// Transfert de propriété
unique_ptr<vector<int>> nouveauPtr = move(ptrVec);
Gestion de tibleaux :
unique_ptr<int[]> tableau(new int[10]);
for(int i = 0; i < 10; ++i) {
tableau[i] = i * i;
}
weak_ptr
Référence faible sans affecter la durée de vie :
shared_ptr<string> principal = make_shared<string>("Test");
weak_ptr<string> observateur = principal;
if(auto acces = observateur.lock()) {
// Accès sécurisé si l'objet existe toujours
}
Tableaux dynamiques
Allocation avec new[] :
long* tabLongs = new long[100];
// Initialisation
delete[] tabLongs; // Libération obligatoire
Alternative moderne :
vector<unique_ptr<string>> mots;
mots.push_back(make_unique<string>("dynamique"));
Allocateur personnalisé
Sépare allocation et construction :
allocator<string> alloc;
auto debut = alloc.allocate(5);
auto fin = uninitialized_fill_n(debut, 5, "valeur");
for(auto it = debut; it != fin; ++it) {
alloc.destroy(it);
}
alloc.deallocate(debut, 5);