Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Resource acquisition is initialization

RAII est une discipline pour gérer les ressources présents en C++, Rust, Ada, mais aussi Python (avec with).

Problème en C

void f() {
    Point* p = malloc(sizeof(Point));
    //...
    
    //⚠ là C détruit le pointeur p, mais pas la zone mémoire qui contient le Point
}
void f() {
    Point p = {.x = 5, .y = 3};
    //...
    
    // là C détruit p
}

Bref, C est un enfant qui ne range pas sa chambre automatiquement.

Principe

Le principe RAII consiste à utiliser le mécanisme de destruction pour détruire les ressources. Du coup, l’idée est d’utiliser la programmation objet pour gérer les ressources comme la mémoire. Il n’y a pas que la mémoire comme ressource. Voici une liste de ressources possibles que l’on peut gérer avec RAII :

Type de resourcesConstructeurDestructeur
Mémoiremallocfree
Fichierouvertureferméture
Transaction à une base de donnéesOuvertureCommit (ou Rollback si erreur)
MutexVérouillageDévérouillage
Rendu graphiquebegin_frameend_frame

Bref, l’acquisition et la libération d’une ressource est associée au cycle de vie d’un objet. Magnifique !

Programmation objet en C++

Elle est similaire à Python. Les différences sont :

  • on a des champs et méthodes privées / protégées / publics
  • on a surtout, en plus du constructeur, un destructeur.

Définition d’une classe

class RadixTree {
    RadixTree() {
        //constructeur
    }

    ~RadixTree() {
        //destructeur
    }
}

Durée de vie

void f() {
    Point p = {.x = 5, .y = 3};
    //...
    
    // là C détruit p
}

C++ appelle le destructeur au moment où la mémoire est désalloué (là quand on dépile le contexte sur la pile d’appel, comme en C). Du coup, avec constructeur et destructeur, on peut gérer les ressources, sachant que le compilateur va faire que le destructeur est appelé dès que l’objet est détruit :

  • au constructeur, on alloue la mémoire, on ouvre un fichier
  • au destructeur, on libère la mémoire, on ferme un fichier

En plus C++ a un système d’exception, compatible avec la durée de vie des objets ! Donc si une exception est levé, la mémoire est bien libérée et le destructeur est appelé.

Avantages

  • A l’utilisation des ressources, le code est léger. Pas besoin d’écrire explicitement freeRadixTree(...)
  • Impossible d’oublier de libérer la mémoire, même en cas d’exception car c’est fait dans le destructeur
  • C’est bien organisé. On sait où trouver la création et la libération de ressources : respectivement dans le constructeur et le destructeur.
  • C’est sans surprise. On applique RAII dans tout son projet.

Terminologie

Utiliser les constructeurs et destructeurs pour gérer les ressources s’appelle “Resource acquisition is initialization” (RAII). D’autres personnes appellent ça “scope bound resource management”.

RAII est utilisé en C++ depuis la version C++11 et en Rust.

En Python

En Python, RAII est implémenté avec with.

class FileWrapper:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        return False

with FileWrapper('data.txt', 'w') as f:
    f.write('Hello World')
    # Si une exception survient ici, __exit__ est tout de même appelé
    # et le fichier est bien fermé