Règle des cinq

Parcourez pas à pas le destructeur, la copie et le déplacement d'une liste chaînée C++.

Tout est simulé dans ce navigateur ; rien n'est envoyé à un serveur.

Opérations

Appel du constructeur

Appel du constructeur

l1Non construite
∅
  • Actuel
  • Pointeur

Étape 1 / 26

l1
l2
l3

Code

#include <iostream>
#include <utility>
class Nodo { // Un nœud contient la donnée et un pointeur vers le suivant
private: // Attributs privés : seules les méthodes y touchent
int dato_;
Nodo* siguiente_;
public:
Nodo(int dato, Nodo* siguiente = nullptr) {
dato_ = dato;
siguiente_ = siguiente;
}
int getDato() const { return dato_; }
void setDato(int dato) { dato_ = dato; }
Nodo* getSiguiente() const { return siguiente_; }
void setSiguiente(Nodo* siguiente) { siguiente_ = siguiente; }
};
class Lista {
private:
Nodo* cabeza_; // Premier nœud de la liste
int n_; // Nombre de nœuds
public:
Lista() { // Constructeur par défaut : crée une liste vide
cabeza_ = nullptr;
n_ = 0;
}
~Lista() { // Destructeur : libère tous les nœuds
while (cabeza_ != nullptr) {
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
}
Lista(const Lista& origin) { // Constructeur de copie : fait une copie profonde
cabeza_ = nullptr;
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarElemento(aux->getDato());
aux = aux->getSiguiente();
}
invertirLista(); // L'insertion en tête inverse l'ordre, on le rétablit donc
}
Lista& operator=(const Lista& origin) { // Affectation par copie : libère les anciens nœuds, puis copie
if (this != &origin) { // Protège contre l'auto-affectation
while (cabeza_ != nullptr) { // Libère les nœuds que la liste possède déjà
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
n_ = 0;
Nodo* aux = origin.cabeza_;
while (aux != nullptr) {
insertarElemento(aux->getDato());
aux = aux->getSiguiente();
}
invertirLista();
}
return *this;
}
Lista(Lista&& origin) noexcept { // Constructeur de déplacement : prend les nœuds de la source
cabeza_ = origin.cabeza_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Laisse la source vide
origin.n_ = 0;
}
Lista& operator=(Lista&& origin) noexcept { // Affectation par déplacement : libère les anciens nœuds, puis prend les nouveaux
if (this != &origin) { // Protège contre l'auto-affectation
while (cabeza_ != nullptr) { // Libère les nœuds que la liste possède déjà
Nodo* borra = cabeza_;
cabeza_ = borra->getSiguiente();
delete borra;
}
cabeza_ = origin.cabeza_;
n_ = origin.n_;
origin.cabeza_ = nullptr; // Laisse la source vide
origin.n_ = 0;
}
return *this;
}
int getTamano() const { return n_; }
Nodo* getCabeza() const { return cabeza_; }
void insertarElemento(int valor) { // Insertion en tête : O(1)
Nodo* nodo = new Nodo(valor, nullptr);
n_ += 1;
nodo->setSiguiente(cabeza_);
cabeza_ = nodo;
}
void imprimirLista() const { // Affiche tous les éléments
Nodo* siguiente = cabeza_;
for (int i = 0; i < n_; i++) {
std::cout << siguiente->getDato() << " ";
siguiente = siguiente->getSiguiente();
}
std::cout << std::endl;
}
void eliminarNodo(int indice) { // Supprime le nœud à l'indice donné
if (indice < 0 || indice >= n_) return; // Indice invalide : rien ne se passe
if (indice == 0) {
Nodo* eliminar = cabeza_;
cabeza_ = eliminar->getSiguiente();
delete eliminar;
n_--;
return;
}
Nodo* eliminanodo_anterio = cabeza_;
for (int i = 0; i < indice - 1; i++) {
eliminanodo_anterio = eliminanodo_anterio->getSiguiente();
}
Nodo* eliminanodo = eliminanodo_anterio->getSiguiente();
eliminanodo_anterio->setSiguiente(eliminanodo->getSiguiente());
delete eliminanodo;
n_--;
}
void invertirLista() { // Inverse la liste sur place
Nodo* anterior = nullptr;
Nodo* actual = cabeza_;
Nodo* siguiente = nullptr;
for (int i = 0; i < n_; i++) {
siguiente = actual->getSiguiente();
actual->setSiguiente(anterior);
anterior = actual;
actual = siguiente;
}
cabeza_ = anterior;
}
};
int main() {
Lista l1;
l1.insertarElemento(10);
l1.insertarElemento(20);
l1.insertarElemento(30);
Lista l2;
l2.insertarElemento(100);
l2 = l1; // Affectation par copie (sans swap)
l2.imprimirLista();
Lista l3;
l3 = std::move(l1); // Affectation par déplacement (sans swap)
l3.imprimirLista();
return 0;
}

Ce visualiseur de la règle des cinq montre comment une liste chaînée C++ gère sa mémoire. Trois listes, l1, l2 et l3, sont côte à côte pour les construire, insérer et supprimer des nœuds, les inverser et les détruire. Essayez ensuite les cinq membres spéciaux : le destructeur, le constructeur de copie, l'affectation par copie, le constructeur de déplacement et l'affectation par déplacement. Chaque opération se déroule pas à pas avec la ligne C++ correspondante en surbrillance, des adresses simulées et le nœud détaché pendant que les pointeurs sont reconnectés. Vous verrez qu'une copie crée de nouveaux nœuds, qu'un déplacement prend les anciens et laisse la source vide, et pourquoi l'auto-affectation est protégée. Tout est simulé dans votre navigateur.

Comment l'utiliser

  1. Choisissez un exemple dans la liste ou commencez par le premier, qui construit une liste puis la détruit.
  2. Ajoutez des opérations à l'une des trois listes avec les boutons ; les constructeurs ne fonctionnent que sur les listes pas encore construites.
  3. Choisissez la liste source à côté de chaque ligne pour les copies et les déplacements, et saisissez une valeur ou un indice si besoin.
  4. Appuyez sur Lecture ou avancez pas à pas, et suivez la ligne C++ en surbrillance et les adresses des nœuds.

Questions fréquentes

Qu'est-ce que la règle des cinq en C++ ?
Si une classe gère une ressource, elle a généralement besoin de cinq membres spéciaux : destructeur, constructeur de copie, affectation par copie, constructeur de déplacement et affectation par déplacement. Cet outil montre chacun d'eux sur une liste chaînée.
Quelle différence entre copier et déplacer une liste ?
Une copie crée de nouveaux nœuds avec de nouvelles adresses et laisse la source intacte. Un déplacement prend les nœuds de la source, garde leurs adresses et laisse la source vide.
Que se passe-t-il avec l1 = l1 ?
Le test this != &origin détecte l'auto-affectation et l'opérateur ne fait rien, ce qui évite de libérer la liste par erreur. L'outil montre ce cas dans son propre exemple.
Le code C++ est-il vraiment compilé ?
Non. La page simule chaque opération dans votre navigateur et met en surbrillance la ligne correspondante du code ; rien n'est envoyé à un serveur.

Outils associés