Pourquoi les projets C++ ont-ils besoin de feature flags ? Analysons en pratique
\n\nImaginez : vous écrivez une bibliothèque de traitement d'images. Une nouvelle version de l'algorithme de compression offre +30% de vitesse, mais nécessite des ajustements pour des architectures de processeurs spécifiques. La publier immédiatement est risqué. Ne pas la publier vous fait perdre un avantage concurrentiel. Un dilemme familier ?
\n\nDans le monde du développement web, ce problème est résolu depuis longtemps avec les feature flags (également appelés feature toggles ou drapeaux de fonctionnalités). Mais dans l'écosystème C++, on les aborde avec méfiance : « Ce n'est pas du PHP, ici on a de la compilation native, chaque if compte ». Et c'est une erreur. Les feature flags en C++ ne sont pas une question de ralentissement, mais de contrôle. Voyons comment les intégrer sans douleur, tout en préservant les performances et le bon sens.
\n\nQue sont les feature flags et pourquoi sont-ils nécessaires en C++
\n\nUn feature flag est un interrupteur conditionnel qui permet d'activer ou de désactiver une fonctionnalité sans modifier le code ni recompiler. En C++, c'est particulièrement pertinent en raison des spécificités du langage :
\n\n- \n
- Compilation longue — recompiler tout le projet pour un seul flag est coûteux. \n
- Plateformes différentes — une fonctionnalité peut fonctionner sous Linux et planter sous Windows. \n
- Tests A/B — vous voulez tester un nouveau parseur ? Activez-le pour 5% des utilisateurs. \n
- Déploiements canary — déployez des fonctionnalités sur une petite audience sans impacter tout le monde. \n
La principale crainte des développeurs C++ est que les flags encombrent le code et tuent les performances. Mais avec une approche intelligente, les toggles en C++ deviennent un outil élégant, et non une béquille.
\n\nImplémentation la plus simple : de #define aux flags runtime
\n\nLa méthode la plus primitive, encore rencontrée dans du code legacy :
\n\n#define USE_NEW_ALGORITHM 1\n\nvoid processImage(Image& img) {\n#if USE_NEW_ALGORITHM\n // nouvel algorithme rapide\n newCompress(img);\n#else\n // ancien algorithme lent\n oldCompress(img);\n#endif\n}\n\nLes inconvénients sont évidents : pour basculer, il faut modifier le code et recompiler. En prime, zéro flexibilité. N'essayez même pas de faire cela en production.
\n\nLa bonne voie, ce sont les flags runtime. Une version minimale fonctionnelle :
\n\n#include <atomic>\n#include <string>\n\nclass FeatureFlag {\npublic:\n explicit FeatureFlag(bool enabled) : enabled_(enabled) {}\n \n bool isEnabled() const { return enabled_.load(); }\n void setEnabled(bool val) { enabled_.store(val); }\n \nprivate:\n std::atomic<bool> enabled_{false};\n};\n\n// Registre global de flags\nclass FeatureFlags {\npublic:\n static FeatureFlag& get(const std::string& name) {\n static std::unordered_map<std::string, FeatureFlag> flags;\n auto it = flags.find(name);\n if (it == flags.end()) {\n it = flags.emplace(name, false).first;\n }\n return it->second;\n }\n};\n\nUtilisation :
\n\nif (FeatureFlags::get("new_compression").isEnabled()) {\n newCompress(img);\n} else {\n oldCompress(img);\n}\n\nC'est mieux. Mais il y a un hic : std::unordered_map avec un mutex à l'intérieur n'est pas le meilleur ami du high-load. Et les clés de type chaîne à chaque if sont discutables.
Approche avancée : flags par énumération et constexpr
\n\nPour du code de production, je recommande une approche hybride. Des identifiants statiques à la compilation, et des valeurs provenant d'une configuration runtime.
\n\n#include <array>\n#include <atomic>\n#include <cstdint>\n\nenum class Feature : uint8_t {\n NewCompression,\n ParallelProcessing,\n EnhancedLogging,\n ExperimentalParser,\n COUNT // toujours en dernier\n};\n\nclass FlagManager {\npublic:\n static FlagManager& instance() {\n static FlagManager mgr;\n return mgr;\n }\n \n bool isEnabled(Feature f) const {\n