HOWTO · C++

#pragma once en C++ : gardes d’inclusion et portabilité

Empêchez les inclusions répétées d’en-têtes C++ avec #pragma once ou des gardes d’inclusion.

Utilisez #pragma once au début d’un en-tête C ou C++ ordinaire lorsque tous les compilateurs pris en charge l’implémentent. Il traite ce fichier au plus une fois par unité de traduction. Préférez une garde d’inclusion #ifndef/#define portant un nom unique pour la portabilité standard, un compilateur inconnu ou une convention existante. Aucun de ces mécanismes ne limite l’en-tête à une seule utilisation dans tout le programme.

Mettre #pragma once avant les déclarations

// config.hpp
#pragma once

class Config {
public:
    int port() const { return 8080; }
};

La directive n’a pas de point-virgule. Le préprocesseur traite #include avant la compilation ; #pragma once évite donc de traiter à nouveau ce texte lors de la construction d’une unité de traduction. C’est une extension largement implémentée, notamment par GCC, Clang et MSVC, mais non standard ISO C/C++. GCC la décrit comme alternative aux en-têtes à inclusion unique : vérifiez néanmoins les toolchains réellement visées.

Vérifier une inclusion directe et indirecte

Ce jeu exact de trois fichiers inclut config.hpp via server.hpp et directement depuis main.cpp :

// server.hpp
#pragma once
#include "config.hpp"

class Server { Config config_; };
// main.cpp
#include "server.hpp"
#include "config.hpp"

int main() {
    return Config{}.port() == 8080 ? 0 : 1;
}

Enregistrez les trois fichiers dans le même dossier, puis exécutez :

g++ -std=c++17 -Wall -Wextra main.cpp -o app
./app

Avec #pragma once dans config.hpp, il n’y a pas de sortie et ./app renvoie 0. Le jeu a été vérifié avec g++ (Ubuntu 15.2.0-16ubuntu1) 15.2.0. Retirez seulement la directive : GCC renvoie 1 et signale la redéfinition de Config, avec l’inclusion directe et l’inclusion indirecte antérieure. La règle est par unité de traduction ; MSVC et Clang n’ont pas été testés séparément.

Utiliser une garde d’inclusion standard

// config.hpp
#ifndef EXAMPLE_CONFIG_HPP
#define EXAMPLE_CONFIG_HPP

class Config {
public:
    int port() const { return 8080; }
};

#endif  // EXAMPLE_CONFIG_HPP

La première inclusion définit le macro et les suivantes sautent le texte. Choisissez un nom descriptif et unique, issu du projet, du dossier et du fichier. CONFIG_H peut entrer en collision avec un autre en-tête ; __ et _ suivi d’une majuscule sont réservés à l’implémentation.

#include est une inclusion textuelle : avant la vérification C++, le préprocesseur remplace la directive par le texte de l’en-tête. Dans l’exemple, config.hpp arrive d’abord par server.hpp, puis directement dans main.cpp ; sans protection, le compilateur reçoit deux définitions de Config. La protection doit donc être dans l’en-tête, pas seulement dans un fichier source qui l’inclut. Il s’agit d’une erreur de compilation, distincte d’une erreur d’édition de liens.

Choisir le mécanisme

Situation Choix Raison
Tous les compilateurs cibles acceptent #pragma once #pragma once Concis, sans collision de macro.
Bibliothèque publique, compilateur inconnu ou portabilité stricte Garde d’inclusion Directives standard.
Convention du dépôt Cette convention Des en-têtes cohérents sont plus faciles à maintenir.
En-tête inclus volontairement plusieurs fois, par exemple une liste X-macro Aucun par défaut Chaque inclusion est voulue.

N’ajoutez pas les deux mécanismes à chaque en-tête : un seul suffit normalement et les compilateurs reconnaissent les gardes usuelles. Ne supposez pas non plus un gain de vitesse universel : mesurez-le. #pragma once dépend de l’identité de fichier reconnue par l’implémentation ; alias, fichiers générés, systèmes réseau ou chemins inhabituels peuvent compter. Les gardes évitent cette question, mais exigent un macro unique.

Ce que la protection ne résout pas

Elle agit par unité de traduction et ne corrige ni toutes les erreurs d’édition de liens par définitions multiples ni la One Definition Rule (ODR). Une fonction libre non inline définie dans un en-tête peut produire une définition externe par .cpp : mettez les définitions ordinaires dans un .cpp, ou utilisez délibérément inline ou des modèles. Elle ne résout pas non plus un cycle où les deux types doivent être complets ; employez une déclaration anticipée pour un pointeur ou une référence, ou déplacez l’implémentation. Les modules C++20 sont différents : import n’inclut pas du texte d’en-tête.

Résumé

Utilisez #pragma once quand l’extension est acceptable, ou une garde d’inclusion unique pour la portabilité standard. Vérifiez le chemin direct et indirect, puis traitez séparément ODR, cycles, en-têtes à inclusions répétées et modules.