Programmation • IoT

Résumé – Chap 10 – Rule of Zero

Lorsqu’une classe gère elle-même une ressource dynamique, le destructeur n’est rapidement plus la seule fonction spéciale dont il faut se préoccuper. Une classe contenant un pointeur vers une zone allouée dynamiquement peut libérer cette mémoire dans son destructeur, mais la copie par défaut ne copie que la valeur du pointeur : deux objets deviennent alors propriétaires de la même zone mémoire et tenteront finalement de la libérer tous les deux. Une gestion correcte impose une copie profonde ainsi qu’un opérateur d’affectation adapté, ce qui conduit à la Rule of Three : lorsqu’une classe doit définir un destructeur, un constructeur de copie ou un opérateur d’affectation par copie, elle doit généralement considérer les trois ensemble. Avec la sémantique de déplacement introduite en C++11 s’ajoutent le constructeur et l’opérateur d’affectation par déplacement. Ceux-ci peuvent transférer une ressource au lieu de la recopier, en laissant l’objet source dans un état valide ne possédant plus cette ressource. Le mot-clé noexcept joue alors un rôle important, notamment pour permettre aux conteneurs de la bibliothèque standard d’utiliser efficacement le déplacement lors de leurs réallocations. L’ensemble formé par le destructeur, les deux opérations de copie et les deux opérations de déplacement constitue la Rule of Five. Ces règles montrent surtout combien la gestion manuelle d’une ressource oblige une classe à prendre en charge des mécanismes délicats dont la moindre erreur peut provoquer fuite mémoire, double libération ou comportement indéfini.

La Rule of Zero propose une conception différente : autant que possible, une classe ne devrait définir elle-même aucune de ces fonctions spéciales pour gérer ses ressources. Elle délègue cette responsabilité à des types qui appliquent déjà correctement le RAII, comme std::vector, std::string, std::unique_ptr ou std::shared_ptr. Une classe qui remplace par exemple un pointeur dynamique et une taille par un std::vector bénéficie automatiquement d’une destruction correcte, d’une copie profonde, d’une affectation et d’un déplacement appropriés, sans avoir à réécrire ces opérations. Le compilateur peut alors générer les fonctions spéciales nécessaires à partir des membres de la classe, et leur comportement est naturellement celui des objets qu’elle contient. La Rule of Zero ne signifie pas qu’une classe ne doit posséder aucun constructeur : les constructeurs ordinaires nécessaires à son initialisation et à sa logique restent parfaitement légitimes. Elle signifie que la gestion technique de la durée de vie des ressources doit, lorsque cela est possible, être confiée à des objets conçus pour cette tâche. Une classe utilisant unique_ptr peut ainsi devenir naturellement non copiable mais déplaçable, conformément au modèle de propriété choisi. Cette approche améliore l’encapsulation et réduit considérablement la quantité de code consacré à la gestion des ressources, donc le nombre d’occasions d’introduire des erreurs. L’étude préalable de new, delete, de la copie profonde, du déplacement et des Rules of Three et Five reste néanmoins essentielle pour comprendre ce que les abstractions modernes prennent en charge. La Rule of Zero apparaît ainsi comme l’aboutissement naturel du RAII : plutôt que de gérer correctement les ressources partout, on conçoit les classes de façon à ne plus avoir à les gérer manuellement.