Le secret des macros C++ : pourquoi utiliser do-while(0) et if-else dans les macros et 3 erreurs fatales

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Les macros en C++ : beauté dangereuse ou mal nécessaire ?

\n\n

Quiconque a déjà écrit en C++ a rencontré les macros. D'un côté, c'est un outil puissant pour la métaprogrammation qui peut réduire la quantité de code passe-partout. De l'autre, les macros sont réputées pour leur caractère perfide. Il suffit d'oublier un point-virgule ou de mal placer des parenthèses pour que le code se transforme en un monstre imprévisible.

\n\n

Mais il existe dans le monde du C++ une catégorie particulière de macros qui semble franchement étrange. Avez-vous déjà vu ce code ?

\n\n
#define SAFE_DELETE(ptr) do { delete (ptr); (ptr) = nullptr; } while(0)
\n\n

C'est logique : supprimer le pointeur et le mettre à null. Mais pourquoi cette boucle do-while(0) apparemment inutile ? Une boucle qui s'exécute forcément une seule fois ? Cela ressemble à une redondance frôlant l'absurde. Ou encore cet exemple avec if-else :

\n\n
#define LOG_ERROR(msg) if (g_logging) std::cerr << (msg)
\n\n

Tout semble simple. Mais c'est ici que se cachent les pièges dans lesquels des milliers de développeurs sont tombés. Cette question sur Stack Overflow a recueilli des dizaines de milliers de vues, et aujourd'hui nous allons voir pourquoi ces constructions ne sont pas un bug mais une fonctionnalité, et quelles erreurs de macros C++ elles évitent.

\n\n

Pourquoi les macros ne sont pas des fonctions ? Le problème principal

\n\n

Avant de plonger dans les méandres du do-while(0), rappelons ce qui distingue une macro d'une fonction classique. En C++, une macro est une substitution textuelle. Le préprocesseur copie simplement le corps de la macro à l'endroit où elle est appelée. Aucune vérification de type, aucune portée — une simple substitution de chaînes.

\n\n

C'est précisément pour cela que surviennent les erreurs classiques dans les macros C++. Prenons un exemple simple :

\n\n
#define SQUARE(x) x * x\n\nint main() {\n    int a = 5;\n    int result = SQUARE(a + 1); // On attend 36, on obtient 11\n    std::cout << result; // Affiche 11\n    return 0;\n}
\n\n

Que s'est-il passé ? La macro s'est développée en a + 1 * a + 1. À cause de la priorité des opérateurs, la multiplication a été effectuée avant l'addition. Première erreur : absence de parenthèses. Corrigeons :

\n\n
#define SQUARE(x) ((x) * (x))
\n\n

Maintenant, ça fonctionne. Mais ce n'est que le début. Les vrais problèmes commencent lorsque la macro contient plusieurs instructions ou des structures de contrôle.

\n\n

L'idiome do-while(0) : un bouclier contre les catastrophes syntaxiques

\n\n

Revenons à notre SAFE_DELETE. Pourquoi ne pas écrire la macro plus simplement, sans cette boucle étrange ?

\n\n
#define SAFE_DELETE(ptr) delete (ptr); (ptr) = nullptr;
\n\n

Cela semble logique. Mais imaginez que vous l'utilisiez dans ce contexte :

\n\n
if (ptr != nullptr)\n    SAFE_DELETE(ptr);\nelse\n    std::cout << "Pointer is null";
\n\n

Après substitution, le préprocesseur transforme cela en :

\n\n
if (ptr != nullptr)\n    delete (ptr);\n    (ptr) = nullptr;\nelse\n    std::cout << "Pointer is null";
\n\n

Et voilà le bug ! Le else n'est plus rattaché au if car une deuxième instruction est apparue entre eux. Le compilateur générera une erreur, mais l'idée est claire : la macro a « cassé » la structure de contrôle. C'est exactement pour ces cas que do-while(0) est nécessaire. Il transforme plusieurs instructions en une seule instruction composée, préservant la logique :

\n\n
if (ptr != nullptr)\n    do { delete (ptr); (ptr) = nullptr; } while(0);\nelse\n    std::cout << "Pointer is null";
\n\n

Maintenant, tout fonctionne correctement. L'idiome des macros do-while(0) est une technique standard qui garantit que la macro se comporte comme une instruction unique.

\n\n

Pourquoi spécifiquement do-while(0) et pas simplement un bloc { } ?

\n\n

Bonne question ! On pourrait simplement utiliser des accolades :

\n\n
#define SAFE_DELETE(ptr) { delete (ptr); (ptr) = nullptr; }
\n\n

Essayons d'utiliser

Blogs

Book Recommendations