现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.
Normes de codage (Guide de style Google C++)
- Exemple de norme de codage (Google C++ Style Guide)
- 0.Contexte
- 1.Fichier d'en-tête
- 2.Portée
- 3. Classe
-
- 3.1. Responsabilités du Constructeur
- 3.2.Constructeurs par défaut
- 3.3. Constructeurs explicites
- 3.4.Copier les constructeurs
- 3.5. Structures et classes (Structs vs. Classes)
- 3.6. Héritage
- 3.7.Héritage multiple
- 3.8.Interface
- 3.9. Surcharge de l'opérateur
- 3.10.Contrôle d'accès
- 3.11.Ordonnance de déclaration
- 3.12. Écrire des fonctions courtes
- 3.13.Résumé
- 4. Expérience de Google
- 5. Autres fonctionnalités C++
-
- 5.1.Arguments de référence
- 5.3.Arguments par défaut
- 5.4. Tableaux de longueur variable et alloca()
- 5.5.Amis
- 5.6.Exceptions
- 5.7. Informations sur le type d'exécution (RTTI)
- 5.8. Conversion de type (Casting)
- 5.9. Flux
- 5.10. Pré-incrémentation et pré-décrémentation
- 5.11.Utilisation de const
- 5.12.Types entiers
- Portabilité 5.13.64 bits
- 5.14. Macros du préprocesseur
- 5.16.taillede
- 5.17.Bibliothèque Boost (Boost)
- 5.18.Résumé
- 6. Convention de dénomination
- 7. Commentaires
- 8.Format
-
- 8.1.Longueur de la ligne
- 8.2.Caractères non-ASCII
- 8.3. Espaces et tabulations
- 8.4. Déclarations et définitions de fonctions
- 8.5. Appels de fonction
- 8.6. Déclarations conditionnelles (Conditionnelles)
- 8.7. Boucles et instructions Switch
- 8.8. Pointeurs et expressions de référence
- 8.9.Expressions booléennes
- 8.10. Valeurs de retour de fonction (Valeurs de retour)
- 8.11. Initialisation des variables et des tableaux
- 8.12. Directives du préprocesseur
- 8.13. Format des cours
- 8.14.Listes d'initialisation
- 8.15. Formatage de l'espace de noms
- 8.16. Espaces horizontaux
- 8.17. Espaces verticaux
- 8.18.Résumé
- 9.Exceptions aux règles
Exemple de norme de codage (Google C++ Style Guide)
0.Contexte
La plupart des projets open source de Google sont développés en C++. Chaque programmeur C++ sait également que le langage C++ possède de nombreuses fonctionnalités puissantes, mais cette puissance conduit inévitablement à sa complexité. Cette complexité rendra le code plus sujet aux bugs et difficile à lire et à maintenir.
Le but de ce guide est de contourner sa complexité en détaillant ce qu'il faut faire et ce qu'il ne faut pas faire lors du codage en C++. Ces règles facilitent la gestion tout en permettant au code d'utiliser efficacement les fonctionnalités du langage C++.
Le style, également appelé lisibilité, fait principalement référence aux habitudes qui régissent le code C++. Utiliser le terme style est un peu abusif, car ces habitudes vont bien au-delà des formats de fichiers de code source.
Une façon de rendre votre code gérable consiste à améliorer sa cohérence. Il est important de rendre votre code compréhensible pour les autres. Le maintien d'un style de programmation unifié signifie que la signification de divers symboles peut être facilement déduite sur la base de règles de « correspondance de modèles ». Créer des idiomes et des modèles communs et nécessaires peut rendre le code plus facile à comprendre. Dans certains cas, modifier certains styles de programmation peut être un bon choix, mais nous devons toujours suivre le principe de cohérence et essayer de ne pas le faire.
Un autre point soulevé dans ce guide est la surabondance de fonctionnalités du C++. Le C++ est un langage géant qui contient de nombreuses fonctionnalités avancées. Dans certains cas, nous restreindrons, voire interdireons l'utilisation de certaines fonctionnalités pour simplifier le code et éviter divers problèmes qui pourraient en résulter. Le guide répertorie ces fonctionnalités et explique pourquoi celles-ci les fonctionnalités sont d’une utilité limitée.
Les projets open source développés par Google respecteront ces directives.
Remarque : Ce guide n'est pas un didacticiel C++, nous supposons que le lecteur est déjà familier avec le C++.
1.Fichier d'en-tête
Habituellement, chaque fichier .cc (fichier source C++) a un fichier .h correspondant (fichier d'en-tête C++), il existe quelques exceptions, telles que le code de test unitaire et les fichiers .cc qui contiennent uniquement main().
Une utilisation appropriée des fichiers d'en-tête peut considérablement améliorer la lisibilité du code, la taille des fichiers et les performances.
Les règles suivantes vous guideront pour éviter divers problèmes lors de l'utilisation des fichiers d'en-tête.
1.1. Protection de #define
Tous les fichiers d'en-tête doivent utiliser #define pour empêcher les inclusions multiples de fichiers d'en-tête. Le format de dénomination doit être :
garantir l'unicité, le fichier d'en-tête doit être nommé en fonction du chemin complet de l'arborescence du code source du projet dans lequel il se trouve. Par exemple, le fichier d'en-tête foo/src/bar/baz.h dans le projet foo est protégé comme suit :
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
…
#endif // FOO_BAR_BAZ_H_
1.2. Dépendances du fichier d'en-tête
Utilisez des déclarations forward pour minimiser le nombre de #includes dans les fichiers .h.
Lorsqu'un fichier d'en-tête est inclus, une nouvelle dépendance est également introduite. Tant que le fichier d'en-tête est modifié, le code doit être recompilé. Si votre fichier d'en-tête comprend d'autres fichiers d'en-tête, toute modification apportée à ces fichiers d'en-tête entraînera également la recompilation du code qui inclut votre fichier d'en-tête. Par conséquent, nous préférons inclure le moins de fichiers d’en-tête possible, en particulier ceux inclus dans d’autres fichiers d’en-tête.
L'utilisation de déclarations avancées peut réduire considérablement le nombre de fichiers d'en-tête à inclure. Par exemple : la classe File est utilisée dans le fichier d'en-tête, mais il n'est pas nécessaire d'accéder à la déclaration File, alors la classe File doit seulement être pré-déclarée dans le fichier d'en-tête ; il n'est pas nécessaire de #include "file/ base/fichier.h".
Comment puis-je utiliser la classe Foo dans un fichier d'en-tête sans accéder à la définition de la classe ?
- Déclarez le type de membre de données comme Foo * ou Foo &;
- Les fonctions avec des paramètres et des types de valeurs de retour de Foo sont uniquement déclarées (mais l'implémentation n'est pas définie) ;
- Le type de données membres statiques peut être déclaré comme Foo car les données membres statiques sont définies en dehors de la définition de classe.
D'un autre côté, si votre classe est une sous-classe de Foo ou contient des données membres non statiques de type Foo, vous devez inclure un fichier d'en-tête pour celle-ci.
Parfois, il est judicieux d'utiliser des membres pointeurs (mieux encore scoped_ptr) au lieu de membres objets. Cependant, cette approche réduira la lisibilité du code et l’efficacité de son exécution. S'il s'agit simplement d'inclure moins de fichiers d'en-tête, il vaut mieux ne pas les remplacer ainsi.
Bien entendu, le fichier .cc a de toute façon besoin de la partie définition de la classe utilisée, et il contiendra naturellement plusieurs fichiers d'en-tête.
Note du traducteur : ne vous fiez pas aux définitions si vous pouvez vous fier aux déclarations.
1.3.Fonctions en ligne
Ce n'est que lorsqu'une fonction comporte 10 lignes ou moins qu'elle est définie comme fonction en ligne.
Définition : lorsqu'une fonction est déclarée comme fonction en ligne, le compilateur peut la développer en ligne sans appeler la fonction en ligne selon le mécanisme d'appel de fonction habituel.
Avantages : lorsque le corps de la fonction est relativement petit, l'intégration de la fonction peut rendre le code cible plus efficace. Pour les fonctions d'accès (accesseur, mutateur) et autres fonctions d'exécution de touches courtes.
Inconvénients : L'abus de l'inlining entraînera un ralentissement du programme. L'inlining peut augmenter ou diminuer la quantité de code cible, en fonction de la taille de la fonction en cours d'inline. L'intégration de fonctions d'accès plus petites réduit généralement la taille du code, mais l'intégration d'une très grande fonction (Note du traducteur : si le compilateur le permet) augmentera considérablement la taille du code. Sur les processeurs modernes, le code plus petit a tendance à s'exécuter plus rapidement en raison d'une meilleure utilisation du cache d'instructions.
Conclusion : une bonne règle de base est de ne pas intégrer de fonctions qui dépassent 10 lignes. Les destructeurs doivent être traités avec prudence, les destructeurs sont souvent plus longs qu'il n'y paraît à cause de certains membres implicites et le destructeur de la classe de base (le cas échéant) est appelé !
Autre règle générale utile : il ne sert à rien d'incorporer des fonctions contenant des boucles ou des instructions switch, à moins que dans la plupart des cas, ces boucles ou instructions switch ne soient jamais exécutées.
Il est important de noter que les fonctions virtuelles et récursives ne sont pas nécessairement des fonctions en ligne même si elles sont déclarées en ligne. Généralement, les fonctions récursives ne doivent pas être déclarées en ligne. La principale raison de l'inclusion du destructeur est qu'il est défini dans la définition de la classe, pour des raisons de commodité ou pour documenter son comportement.
Fichier 1.4.-inl.h
La définition des fonctions en ligne complexes doit être placée dans un fichier d'en-tête avec le suffixe -inl.h.
Donner la définition d'une fonction en ligne dans un fichier d'en-tête permet au compilateur de la développer en ligne sur le site d'appel. Cependant, le code d'implémentation doit être entièrement placé dans le fichier .cc. Nous ne voulons pas que trop de code d'implémentation apparaisse dans le fichier .h à moins qu'il n'y ait des avantages évidents en termes de lisibilité et d'efficacité.
Si la définition de la fonction en ligne est relativement courte et que la logique est relativement simple, son code d'implémentation peut être placé dans le fichier .h. Par exemple, l’implémentation des fonctions d’accès est naturellement placée dans la définition de classe. Pour faciliter l'implémentation et l'appel, des fonctions en ligne plus complexes peuvent également être placées dans des fichiers .h. Si vous pensez que cela rendra le fichier d'en-tête volumineux, vous pouvez également le séparer dans un -inl.h distinct. Cela sépare l'implémentation de la définition de classe et inclut -inl.h où se trouve l'implémentation en cas de besoin.
Le fichier -inl.h peut également être utilisé pour définir des modèles de fonctions, rendant ainsi la définition du modèle plus lisible.
Une chose à rappeler est que -inl.h, comme les autres fichiers d'en-tête, a également besoin d'une protection #define.
1.5. Ordre des paramètres de fonction
Lors de la définition d'une fonction, l'ordre des paramètres est le suivant : les paramètres d'entrée en premier, les paramètres de sortie en dernier.
Les paramètres de fonction C/C++ sont divisés en deux types : les paramètres d'entrée et les paramètres de sortie. Parfois, les paramètres d'entrée sont également sortis (Note du traducteur : lorsque la valeur est modifiée). Les paramètres d'entrée sont généralement transmis par valeur ou référence constante (références const), et les paramètres de sortie ou les paramètres d'entrée/sortie sont des pointeurs non const (pointeurs non const). Lors du tri des paramètres, placez tous les paramètres d’entrée avant les paramètres de sortie. Ne le mettez pas en dernier simplement parce qu'il s'agit d'un paramètre nouvellement ajouté, mais il doit toujours être placé avant les paramètres de sortie.
Ce n'est pas une règle qui doit être suivie, et des paramètres d'entrée/sortie mixtes (généralement des variables de classe/structure) rendront la règle difficile à suivre.
1.6. Inclure le nom et l'ordre des fichiers
Standardiser l'ordre d'inclusion peut améliorer la lisibilité et éviter les dépendances cachées (Note du traducteur : les dépendances cachées font principalement référence aux fichiers inclus lors de la compilation). L'ordre est le suivant : bibliothèque C, bibliothèque C++, .h des autres bibliothèques, projets .h à l'intérieur.
Les fichiers d'en-tête au sein du projet doivent être organisés selon la structure arborescente du code source du projet et éviter d'utiliser les chemins de fichiers UNIX (répertoire actuel) et ... (répertoire parent). Par exemple, google-awesome-project/src/base/logging.h doit être inclus comme ceci :
#include "base/logging.h"
Le rôle principal de dir/foo.cc est d'exécuter ou de tester la fonctionnalité de dir2/ foo2.h, L'ordre des fichiers d'en-tête inclus dans foo.cc est le suivant :
dir2/foo2.h (position prioritaire, les détails sont les suivants)
Fichiers système C
Fichiers système C++
Autres fichiers d'en-tête de bibliothèque Cette méthode de tri des
fichiers d'en-tête dans ce Le projet peut réduire efficacement les dépendances cachées.Nous espérons
que chaque fichier d'en-tête sera compilé indépendamment. Le moyen le plus simple d'y parvenir est de l'inclure comme premier fichier .h dans le .cc correspondant.
dir/foo.cc et dir2/foo2.h se trouvent généralement dans le même répertoire (comme base/basictypes_unittest.cc et base/basictypes.h), mais peuvent également se trouver dans des répertoires différents.
L'ordre alphabétique des fichiers d'en-tête dans le même répertoire est un bon choix.
Par exemple, l'ordre d'inclusion de google-awesome-project/src/foo/internal/fooserver.cc est le suivant :
#include "foo/public/fooserver.h" // Priorité
#include <sys/types.h>
#include <unistd.h>
#include <hash_map>
#include
#include « base/basictypes.h »
#include « base/commandlineflags.h »
#include « foo/public/bar.h »
1.7.Résumé
- Éviter les inclusions multiples est l’exigence la plus fondamentale lors de l’apprentissage de la programmation ;
- La déclaration forward vise à réduire les dépendances de compilation et à empêcher la modification d'un fichier d'en-tête de provoquer un effet domino ;
- L'utilisation raisonnable des fonctions en ligne peut améliorer l'efficacité de l'exécution du code ;
- -inl.h peut améliorer la lisibilité du code (généralement non utilisé :D) ;
- La normalisation de l'ordre des paramètres de fonction peut améliorer la lisibilité et la facilité de maintenance (cela a un léger impact sur l'espace de pile des paramètres de fonction, j'avais l'habitude de les regrouper pour la plupart du même type) ;
- Les noms des fichiers inclus utilisent . et... bien que pratique mais sujet à confusion. L'utilisation d'un chemin de projet relativement complet semble très claire et organisée. En plus d'être beau, l'ordre des fichiers inclus, le plus important est qu'il puisse réduire les dépendances cachées et créer chaque fichier d'en-tête Lors de la compilation à l'endroit qui "a le plus besoin d'être compilé" (correspondant au fichier source : D), certaines personnes suggèrent que le fichier de bibliothèque soit placé à la fin. De cette façon, les fichiers dans le projet échouera en premier et les fichiers d'en-tête seront placés au début du fichier source correspondant. Ceci est suffisant pour garantir que les erreurs internes ont été découvertes rapidement.
2.Portée
2.1. Espaces de noms
Dans les fichiers .cc, il est recommandé d'utiliser des espaces de noms sans nom (Note du traducteur : les espaces de noms sans nom sont comme des classes sans nom, et ils semblent être rarement introduits :-(). Lors de l'utilisation d'espaces de noms nommés, son nom peut être basé sur le projet ou le chemin nom, n'utilisez pas l'indicateur using.
Définition : L'espace de noms subdivise la portée globale en différentes portées nommées, ce qui peut efficacement empêcher les conflits de noms dans la portée globale. Avantages
: L'espace de noms fournit Bien sûr, la classe fournit également un axe de nom (imbriqué) (Note du traducteur : divisez le nom dans différents espaces de noms. classe).
Par exemple, deux projets différents ont une classe Foo dans la portée globale, provoquant des conflits au moment de la compilation ou de l'exécution. Si chaque projet place le code dans un espace de noms différent, projet1 ::Foo Naturellement, il n'y aura pas de conflit avec project2::Foo en tant que symboles différents.
Inconvénients : Les espaces de noms prêtent à confusion car ils fournissent des axes de dénomination supplémentaires (imbriqués) comme les classes. L'utilisation d'espaces sans nom dans les fichiers d'en-tête est facile à violer la règle de définition unique de C++ ( ODR).
Conclusion : Utilisez les espaces de noms de manière appropriée selon les stratégies mentionnées ci-dessous.
2.1.1.Espaces de noms sans nom
Dans les fichiers .cc, l'utilisation d'espaces de noms sans nom est autorisée, voire encouragée, pour éviter les conflits de noms lors de l'exécution :
namespace { // Dans les fichiers .cc
// le contenu de l'espace de noms n'a pas besoin d'être indenté
enum { UNUSED, EOF, ERROR }; / / Symboles fréquemment utilisés
bool AtEof() { return pos_ == EOF; } // Utiliser le symbole EOF dans cet espace de noms
} // Espace de noms
Cependant, les déclarations de portée de fichier associées à une classe spécifique sont déclarées comme types dans cette classe, une donnée membre statique ou une fonction membre statique, plutôt qu'un membre d'un espace de noms sans nom. Comme indiqué ci-dessus, les espaces de noms sans nom se terminent par le commentaire // d'espace de noms.
Les espaces de noms sans nom ne peuvent pas être utilisés dans les fichiers .h.
2.1.2. Espaces de noms nommés
L'espace de noms nommé est utilisé comme suit :
l'espace de noms encapsule l'intégralité du fichier source, à l'exception de l'inclusion du fichier, de la déclaration/définition des identifiants globaux et de la déclaration directe de la classe pour la distinguer des autres espaces de noms.
// .h
espace de noms de fichier monespace de noms { // Toutes les déclarations sont placées dans l'espace de noms // Attention à ne pas utiliser la classe d'indentation MyClass { public: … void Foo(); }; } // espace de noms mon espace de noms // espace de noms de fichier .cc mon espace de noms { // Les définitions de fonctions sont placées dans l'espace de noms void MyClass::Foo() { … } } // Espace de noms mynamespace Habituellement, les fichiers .cc contiennent des détails de plus en plus complexes, y compris des références à des classes dans d'autres espaces de noms. #include "ah" DEFINE_bool(someflag, false, "dummy flag"); class C; // Prédéclaration de la classe C dans l'espace de noms global de l'espace de noms a { class A; } // Classe a dans l'espace de noms a:: L'espace de noms de déclaration directe de A b { …code pour b… // code dans b
} // Espace de noms b
Ne déclarez rien sous l'espace de noms std, y compris les déclarations directes des classes de bibliothèque standard. Déclarer des entités sous std peut conduire à un comportement peu clair, par exemple non portable. Pour déclarer une entité sous la bibliothèque standard, vous devez inclure le fichier d'en-tête correspondant.
Il est préférable de ne pas utiliser la directive using pour garantir que tous les noms sous l'espace de noms peuvent être utilisés normalement.
// Interdit - polluer l'espace de noms
en utilisant l'espace de noms foo ;
l'utilisation peut être utilisée dans des fonctions, des méthodes ou des classes dans des fichiers .cc, des fichiers .h.
// Autorisé : dans les fichiers .cc // Dans les fichiers .h, l'utilisation de ::foo::bar
doit être utilisée à l'intérieur des fonctions, méthodes ou classes ; dans les fichiers .cc, les fichiers .h, les fonctions, méthodes ou classes, ainsi que les alias d'espace de noms peut être utilisé. // Autorisé : dans les fichiers .cc // Dans les fichiers .h, doit être utilisé dans des fonctions, méthodes ou classes
espace de noms fbz = ::foo::bar::baz;
2.2. Classe imbriquée
Lors de l'exposition de classes imbriquées dans le cadre d'une interface, il est préférable de placer la déclaration de la classe imbriquée dans un espace de noms, bien qu'il soit possible de les conserver directement dans la portée globale.
Définition : vous pouvez définir une autre classe au sein d'une classe. Les classes imbriquées sont également appelées classes membres.
class Foo { private : // Bar est une classe membre imbriquée dans Foo class Bar { ... }; } ; Avantages : Utile lorsque les classes imbriquées (membres) ne sont utilisées que dans la classe englobante, en la plaçant dans la portée de la classe imbriquée comme un membre de la classe imbriquée ne polluera pas les autres étendues avec la même classe. Vous pouvez pré-déclarer la classe imbriquée dans la classe imbriquée et définir la classe imbriquée dans le fichier .cc pour éviter d'inclure la définition de la classe imbriquée dans la classe imbriquée, car la définition de la classe imbriquée n'est généralement pertinente que pour l'implémentation. . Inconvénient : les classes imbriquées ne peuvent être déclarées que dans la définition de la classe imbriquée. Par conséquent, tout fichier d'en-tête qui utilise les pointeurs Foo::Bar* doit contenir la déclaration complète de Foo. Conclusion : ne définissez pas les classes imbriquées comme publiques à moins qu'elles ne fassent partie d'une interface, par exemple, une méthode utilise un ensemble d'options de la classe.
2.3. Fonctions non membres, membres statiques et globales
Utilisez des fonctions non membres ou des fonctions membres statiques dans l'espace de noms et essayez de ne pas utiliser de fonctions globales.
Avantages : Dans certains cas, les fonctions non membres et les fonctions membres statiques sont très utiles. Placer des fonctions non membres dans l'espace de noms peut éviter de polluer la portée globale.
Inconvénients : il peut être plus logique de rendre les fonctions non membres et les fonctions membres statiques membres d'une nouvelle classe, en particulier lorsqu'elles ont besoin d'accéder à des ressources externes ou ont des dépendances importantes.
Conclusion :
Il est parfois bénéfique, voire nécessaire, de ne pas confiner une fonction à l'entité de la classe, que ce soit en tant que membre statique ou en tant que fonction non membre. Les fonctions non membres ne doivent pas dépendre de variables externes et doivent être placées autant que possible dans un espace de noms. Plutôt que de créer une classe simplement pour encapsuler un certain nombre de fonctions membres statiques qui ne partagent aucune donnée statique, il est préférable d'utiliser un espace de noms.
Les fonctions définies dans la même unité de compilation qui sont directement appelées par d'autres unités de compilation peuvent introduire des dépendances de couplage et de connexion inutiles ; les fonctions membres statiques y sont particulièrement sensibles. Envisagez d'extraire dans une nouvelle classe ou de placer la fonction dans l'espace de noms d'une bibliothèque distincte.
Si vous avez vraiment besoin de définir une fonction non membre et de l'utiliser uniquement dans un fichier .cc, vous pouvez utiliser un espace de noms sans nom ou une association statique (telle que static int Foo() {…}) pour limiter sa portée.
2.4.Variables locales
Placez les variables de fonction dans la plus petite portée possible et initialisez-les lors de leur déclaration.
C++ permet de déclarer des variables n'importe où dans une fonction. Nous préconisons de déclarer les variables dans la portée la plus petite possible, aussi proche que possible de la première utilisation. Cela rend le code plus facile à lire, ce qui facilite la localisation de l'emplacement de déclaration, du type de variable et de la valeur initiale de la variable. En particulier, l'initialisation doit être utilisée à la place de déclaration+affectation.
int i;
i = f(); // Bad - séparation de l'initialisation et de la déclaration
int j = g(); // Good - déclaration lors de l'initialisation Remarque
: gcc peut s'exécuter correctement pour (int i = 0; i < 10; + +i) (la portée de i est limitée aux boucles for), donc je peux être réutilisé dans d'autres boucles for. Dans les instructions if et while, les déclarations de portée sont également correctes.
while (const char* p = strchr(str, '/')) str = p + 1;
Remarque : si la variable est un objet, son constructeur doit être appelé à chaque fois qu'elle entre dans la portée, et son constructeur doit être appelé tous les le moment où il quitte la portée, son destructeur.
// Implémentation inefficace
pour (int i = 0; i < 1000000; ++i) { Foo f; // Le constructeur et le destructeur sont appelés 1 000 000 fois chacun ! f.DoSomething(i); } Il est beaucoup plus efficace de déclarer des variables similaires en dehors de la portée de la boucle : Foo f; // Le constructeur et le destructeur ne sont appelés qu'une seule fois pour (int i = 0; i < 1000000; ++ i) { f.DoSomething(i); }
2.5.Variables globales
Les variables globales de type classe sont interdites, mais les variables globales de types intégrés sont autorisées. Bien entendu, les variables globales non constantes dans le code multithread sont également interdites. N'utilisez jamais de valeurs de retour de fonction pour initialiser des variables globales.
Malheureusement, l'ordre dans lequel les constructeurs, les destructeurs et les opérations d'initialisation des variables globales sont appelés n'est que partiellement spécifié et peut changer chaque build, conduisant à des bogues difficiles à trouver.
Il est donc interdit d'utiliser des variables globales de type classe (notamment chaîne STL, vecteur, etc.) car leur ordre d'initialisation peut poser des problèmes de construction. Les types intégrés et les structures sans constructeurs composés de types intégrés peuvent être utilisés. Si vous devez utiliser des variables globales de type classe, veuillez utiliser le modèle singleton.
Pour les constantes de chaîne globales, utilisez des chaînes de style C au lieu de chaînes STL :
const char kFrogSays[] = "ribbet" ;
Bien que les variables globales puissent être utilisées dans la portée globale, veillez à y réfléchir à deux fois avant de les utiliser. La plupart des variables globales doivent être des données membres statiques de la classe, soit définies dans un espace de noms sans nom lorsqu'elles sont utilisées uniquement dans un fichier .cc, soit utiliser des associations statiques pour limiter la portée de la variable.
N'oubliez pas que les variables membres statiques sont considérées comme des variables globales, elles ne peuvent donc pas être de type classe !
2.6.Résumé
- L'espace de noms sans nom dans .cc peut éviter les conflits de noms, limiter la portée et éviter d'utiliser directement l'invite using pour polluer l'espace de noms ;
- Les classes imbriquées sont conformes au principe d'utilisation locale, mais elles ne peuvent pas être déclarées dans d'autres fichiers d'en-tête et essaient de ne pas être publiques ;
- Essayez de ne pas utiliser de fonctions globales et de variables globales, tenez compte des restrictions de portée et d'espace de noms et essayez de former des unités de compilation distinctes ;
- N'utilisez pas de types de classe (y compris les conteneurs STL) pour les variables globales (y compris les variables membres statiques) dans les multithreads afin d'éviter les bogues causés par un comportement peu clair.
En plus de prendre en compte la pollution et la lisibilité des noms, l'utilisation de la portée vise principalement à réduire le couplage et à améliorer l'efficacité de la compilation et de l'exécution.
3. Classe
La classe est l’unité de code de base en C++ et est naturellement largement utilisée. Cette section répertorie les choses à faire et à ne pas faire lors de l'écriture d'un cours.
3.1. Responsabilités du Constructeur
Seules les initialisations triviales sont effectuées dans le constructeur. Si possible, utilisez la méthode Init() pour initialiser de manière centralisée des données significatives (non triviales).
Définition : Effectuer des opérations d'initialisation dans le constructeur.
Avantages : Composition pratique, pas besoin de se soucier de savoir si la classe est initialisée.
Inconvénients : les problèmes causés par l'exécution d'opérations dans le constructeur incluent :
- Les erreurs ne sont pas facilement signalées dans les constructeurs et les exceptions ne peuvent pas être utilisées.
- L’échec de l’opération entraînera l’échec de l’initialisation de l’objet, provoquant un état incertain.
- Si une fonction virtuelle est appelée dans le constructeur, l'appel ne sera pas envoyé à l'implémentation de la sous-classe. Même s'il n'y a pas d'implémentation de sous-classe actuellement, cela constituera toujours un danger caché à l'avenir.
- Si quelqu'un créait une variable globale de ce type (bien que contraire aux règles mentionnées dans la section précédente), le constructeur serait appelé avant main(), brisant potentiellement les hypothèses implicites du constructeur. Par exemple, gflags n'a pas encore été initialisé.
Conclusion : si l'objet nécessite une initialisation significative (non triviale), envisagez d'utiliser une méthode Init() supplémentaire et/ou d'ajouter un indicateur de membre pour indiquer si l'objet a été initialisé avec succès.
3.2.Constructeurs par défaut
Si une classe définit plusieurs variables membres et n'a pas d'autres constructeurs, elle doit définir un constructeur par défaut, sinon le compilateur générera automatiquement un constructeur par défaut.
Définition : lors de la création d'un objet sans paramètres, le constructeur par défaut est appelé. Lorsque new[] (pour un tableau) est appelé, le constructeur par défaut est toujours appelé.
Avantages : Les structures sont initialisées à des valeurs « impossibles » par défaut, ce qui facilite le débogage.
Inconvénient : il s’agit d’un travail redondant pour le rédacteur du code.
Conclusion :
si la classe a des variables membres définies et qu'aucun autre constructeur n'est fourni, vous devez définir un constructeur par défaut (sans paramètres). Le constructeur par défaut est plus adapté pour initialiser l'objet et rendre l'état interne de l'objet cohérent et valide.
La raison pour laquelle vous fournissez un constructeur par défaut est la suivante : si vous ne fournissez pas d'autres constructeurs et ne définissez pas de constructeur par défaut, le compilateur en générera automatiquement un pour vous. Le constructeur généré par le compilateur n'initialisera pas l'objet.
Si la classe que vous définissez hérite d'une classe existante et que vous n'ajoutez pas de nouvelles variables membres, vous n'avez pas besoin de définir un constructeur par défaut pour la nouvelle classe.
3.3. Constructeurs explicites
Utilisez le mot-clé C++ explicite pour les constructeurs à argument unique.
Définition : généralement, un constructeur avec un seul paramètre peut être utilisé pour la conversion (note du traducteur : fait principalement référence à une conversion implicite, voir ci-dessous). Par exemple, Foo::Foo(string name) est défini, et quand il doit être passé in Lorsqu'une fonction sur un objet Foo est passée dans une chaîne, le constructeur Foo::Foo(string name) est appelé et convertit la chaîne en un objet temporaire Foo et la transmet à la fonction appelante. Cela semble pratique, mais si vous ne souhaitez pas générer un nouvel objet via la conversion, des problèmes surviendront. Pour éviter une conversion implicite provoquée par l'appel du constructeur, vous pouvez le déclarer comme explicite.
Avantages : Évitez les changements intempestifs.
Inconvénients : Aucun.
Conclusion :
tous les constructeurs à argument unique doivent être explicites. Dans la définition de la classe, ajoutez le mot-clé explicit avant le constructeur à argument unique : explicit Foo(string name);
Exception : dans quelques cas, le constructeur de copie n'a pas besoin d'être déclaré explicite ; une classe intentionnellement utilisée comme classe transparente wrapper pour les autres classes. Ces exceptions doivent être clairement indiquées dans les commentaires.
3.4.Copier les constructeurs
Utilisez le constructeur de copie uniquement lorsque vous devez copier un objet de classe dans votre code ; utilisez DISALLOW_COPY_AND_ASSIGN lorsque la copie n'est pas requise.
Définition : Le constructeur de copie peut être utilisé lors de la création d'un nouvel objet par copie (notamment lors du passage d'un objet par valeur).
Avantages : Le constructeur de copie facilite la copie d'objets. Les conteneurs STL nécessitent que tous les contenus soient copiables et assignables.
Inconvénients : Les copies implicites d’objets en C++ sont à l’origine de nombreux problèmes de performances et bugs. Le constructeur de copie réduit la lisibilité du code. Par rapport au passage par référence, il est plus difficile de suivre les objets passés par valeur et l'endroit où l'objet est modifié devient insaisissable.
Conclusion :
un grand nombre de classes n'ont pas besoin d'être copiables, ni d'un constructeur de copie ou d'un opérateur d'affectation. Malheureusement, si vous ne les déclarez pas activement, le compilateur les générera automatiquement pour vous et les rendra publics.
Vous pouvez envisager d'ajouter un constructeur de copie factice et une opération d'affectation à la partie privée de la classe, avec uniquement des déclarations et aucune définition. Étant donné que ces programmes vides sont déclarés privés, le compilateur signalera une erreur lorsqu'un autre code tentera de les utiliser. Pour plus de commodité, vous pouvez utiliser la macro DISALLOW_COPY_AND_ASSIGN :
// Les macros qui interdisent l'utilisation de constructeurs de copie et d'opérations d'affectation
// doivent être utilisées dans le privé : de la classe
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
TypeName(const TypeName&);
void Operator= (const TypeName&)
class Foo { public : Foo(int f); ~Foo(); private :
DISALLOW_COPY_AND_ASSIGN(Foo);
} ;
Comme mentionné ci-dessus, DISALLOW_COPY_AND_ASSIGN doit être utilisé dans la plupart des cas. Si la classe doit vraiment être copiable, la raison doit être expliquée dans le fichier d'en-tête de la classe, et le constructeur de copie et l'opération d'affectation doivent être correctement défini. Remarque Détecter les situations d'auto-affectation dans Operator=.
Lorsque vous utilisez une classe comme conteneur STL, vous pourriez être tenté de rendre la classe copiable. Dans des situations similaires, ce que vous devriez vraiment faire est d'utiliser un pointeur pour pointer vers l'objet dans le conteneur STL. Vous pouvez envisager d'utiliser std::tr1::shared_ptr.
3.5. Structures et classes (Structs vs. Classes)
Utilisez struct uniquement lorsqu'il n'y a que des données et utilisez class pour tout le reste.
En C++, les mots-clés struct et class ont presque la même signification. Nous leur ajoutons une sémantique artificielle afin de pouvoir raisonnablement choisir quel mot-clé utiliser pour le type de données défini.
Struct est utilisé sur des objets passifs qui contiennent uniquement des données, qui peuvent inclure des constantes associées, mais qui n'ont aucune fonction autre que l'accès aux données membres. La fonction d'accès est implémentée via un accès direct sans appels de méthode. Nous fournissons ici Les méthodes mentionnées sont celles utilisées uniquement pour traiter les données membres, telles que les constructeurs, les destructeurs, Initialize(), Reset() et Validate().
Si vous avez besoin de plus de fonctions, la classe est plus adaptée. Si vous n'êtes pas sûr, utilisez directement la classe.
S'il est combiné avec STL, vous pouvez utiliser struct au lieu de class pour les foncteurs et les traits.
Remarque : Les variables membres des classes et des structures utilisent des règles de dénomination différentes.
3.6. Héritage
L'utilisation de la composition (composition, ndlr, ceci est également souligné à plusieurs reprises par GoF dans "Design Patterns") est généralement plus appropriée que l'utilisation de l'héritage. Si l'héritage est utilisé, utilisez uniquement l'héritage public.
Définition : lorsqu'une sous-classe hérite d'une classe de base, la sous-classe contient les définitions de toutes les données et opérations de la classe de base parent. Dans la pratique C++, l'héritage est principalement utilisé dans deux situations : l'héritage d'implémentation, où la sous-classe hérite du code d'implémentation de la classe parent ; l'héritage d'interface, où la sous-classe hérite uniquement des noms de méthodes de la classe parent.
Avantages : l'implémentation de l'héritage réduit la quantité de code en réutilisant intact le code de la classe de base. L’héritage étant une déclaration au moment de la compilation, les codeurs et les compilateurs peuvent comprendre les opérations et détecter les erreurs. L'héritage d'interface peut être utilisé pour améliorer par programme les fonctionnalités d'une API spécifique d'une classe. Le compilateur peut également détecter des erreurs lorsque la classe ne définit pas l'implémentation nécessaire de l'API.
Inconvénients : pour l'héritage d'implémentation, il devient plus difficile de comprendre l'implémentation car le code pour implémenter la sous-classe est étendu entre la classe parent et la sous-classe. Les sous-classes ne peuvent pas remplacer les fonctions non virtuelles de la classe parent et, bien entendu, elles ne peuvent pas modifier leur implémentation. La classe de base peut également définir certains membres de données, et la disposition physique de la classe de base doit également être distinguée.
Conclusion :
tout héritage doit être public. Si vous souhaitez un héritage privé, vous devez plutôt inclure une instance de classe de base en tant que membre.
N'utilisez pas trop l'héritage d'implémentation ; la composition est généralement plus appropriée. Essayez d'utiliser l'héritage uniquement s'il "est un" ("est-un", note du traducteur, veuillez utiliser la combinaison pour les autres cas "has-a") : si Bar est effectivement "une" sorte de Foo, alors laissez Bar est un sous-classe de Foo.
Si nécessaire, rendez le destructeur virtuel. La nécessité signifie que si la classe a une fonction virtuelle, son destructeur doit être une fonction virtuelle.
Note du traducteur : En ce qui concerne le cas particulier où la sous-classe n'a pas de données membres supplémentaires, ou même la classe parent n'a pas de données membres, la question de savoir si l'appel au destructeur est nécessaire est un débat sémantique. Du point de vue des spécifications de conception de programmation , dans la classe parent contenant les fonctions virtuelles. Dans une classe, il faut absolument définir un destructeur virtuel.
Les fonctions membres limitées aux sous-classes sont protégées. Il convient de noter que les données membres doivent toujours être privées.
Lors de la redéfinition d'une fonction virtuelle dérivée, déclarez-la explicitement comme virtuelle dans la classe dérivée. Cause première : si virtual est omis, le lecteur doit récupérer tous les ancêtres de la classe pour déterminer si la fonction est une fonction virtuelle (Note du traducteur, bien que cela n'affecte pas la nature de cette fonction virtuelle).
3.7.Héritage multiple
Il y a très peu de cas où l'héritage d'implémentations multiples est vraiment nécessaire. L'héritage multiple n'est utilisé que lorsqu'au plus une classe de base contient une implémentation et que les autres classes de base sont de pures classes d'interface avec le suffixe Interface.
Définition : L'héritage multiple permet aux sous-classes d'avoir plusieurs classes de base. Il est nécessaire de faire la distinction entre les classes de base qui sont de pures interfaces et les classes de base qui ont des implémentations.
Avantages : L'héritage d'implémentations multiples vous permet de réutiliser plus de code qu'un héritage unique.
Inconvénients : il est très rare que l'héritage d'implémentations multiples soit vraiment nécessaire. L'héritage d'implémentations multiples semble être une bonne solution et vous pouvez généralement trouver une solution plus claire, plus claire et différente.
Conclusion : l'héritage multiple ne peut être utilisé que si toutes les superclasses sauf la première sont de pures interfaces. Pour garantir qu'il s'agit de pures interfaces, ces classes doivent être suffixées par Interface.
Remarque : Concernant cette règle, il existe une exception sous Windows (Note du traducteur, sera expliquée dans les exceptions aux règles dans le dernier article de cette traduction).
3.8.Interface
Les interfaces font référence à des classes qui répondent à des conditions spécifiques. Ces classes portent le suffixe Interface (non obligatoire).
Définition : Lorsqu’une classe répond aux exigences suivantes, elle est appelée une interface pure :
- Uniquement les fonctions virtuelles pures ("=0") et les fonctions statiques (sauf les destructeurs mentionnés ci-dessous) ;
- Il n’y a pas de données membres non statiques ;
- Aucun constructeur n'est défini. Si tel est le cas, il ne contient pas de paramètres et est protégé ;
- S'il s'agit d'une sous-classe, elle ne peut hériter que des classes qui remplissent les conditions ci-dessus et qui ont Interface comme suffixe.
La classe d'interface ne peut pas être instanciée directement car elle déclare des fonctions virtuelles pures. Pour garantir que toutes les implémentations d'une classe d'interface peuvent être détruites correctement, un destructeur virtuel doit être déclaré pour celle-ci (par exception à la règle 1, le destructeur ne peut pas être une fonction virtuelle pure). Pour des détails spécifiques, veuillez vous référer à la section 12.4 de « Le langage de programmation C++, 3e édition » de Stroustrup.
Avantages : L'utilisation du suffixe Interface permet aux autres de savoir qu'ils ne peuvent pas ajouter de fonctions d'implémentation ou de données membres non statiques à la classe d'interface. Ceci est particulièrement important pour l'héritage multiple. De plus, pour les programmeurs Java, le concept d’interface est profondément ancré dans le cœur des gens.
Inconvénients : Le suffixe Interface augmente la longueur du nom de classe, ce qui gêne la lecture et la compréhension. Dans le même temps, les caractéristiques de l'interface ne doivent pas être exposées aux clients en tant que détails d'implémentation.
en conclusion:. Ce n'est que lorsque les besoins ci-dessus sont satisfaits que la classe se termine par Interface, mais à l'inverse, la classe qui répond aux besoins ci-dessus ne se termine pas nécessairement par Interface.
3.9. Surcharge de l'opérateur
Sauf circonstances particulières, ne surchargez pas les opérateurs.
Définition : Une classe peut définir des opérateurs tels que +, /, etc., afin qu'ils puissent être utilisés directement comme des types intégrés.
Avantages : Rendre le code plus intuitif, tout comme les types intégrés (tels que int), les opérateurs surchargés rendent les noms de fonctions ennuyeux tels que Equals(), Add(), etc. beaucoup plus intéressants. Pour que certaines fonctions de modèle fonctionnent correctement, vous devrez peut-être définir des opérateurs.
Inconvénients : bien que la surcharge d'opérateurs rende le code plus intuitif, elle présente également quelques inconvénients.
- Brouiller votre intuition, vous faire penser que certaines opérations chronophages sont aussi légères que les opérations intégrées ;
- Il est plus difficile de trouver le site d'appel de l'opérateur surchargé. Trouver Equals() est évidemment beaucoup plus simple que l'appel équivalent ==;
- Certains opérateurs peuvent opérer sur des pointeurs, ce qui peut facilement conduire à des bugs. Foo + 4 fait une chose, tandis que &Foo + 4 peut faire une autre chose complètement différente. Dans les deux cas, le compilateur ne signalera pas d'erreur. , ce qui rend le débogage difficile ;
- La surcharge a également des effets secondaires qui peuvent vous surprendre. Par exemple, les classes qui surchargent l'opérateur & ne peuvent pas être déclarées en avant.
Conclusion :
ne surchargez généralement pas les opérateurs, en particulier l'opération d'affectation (operator=) qui est plus insidieuse, il faut donc éviter la surcharge. Si nécessaire, vous pouvez définir des fonctions comme Equals(), CopyFrom(), etc.
Cependant, il existe de rares cas où il est nécessaire de surcharger les opérateurs afin de s'interfacer avec des modèles ou des classes C++ "standard" (comme Operator<<(ostream&, const T&)). Ceci est acceptable si justifié, mais vous devez l'éviter. si possible, de cette façon. En particulier, ne surchargez pas l'opérateur juste pour l'utiliser comme clé dans le conteneur STLou Operator<, vous devez plutôt créer des types de foncteurs d'égalité et de comparaison de taille lors de la déclaration du conteneur.
Certains algorithmes STL nécessitent une surcharge des opérateursVous pouvez le faire en le faisant, et n’oubliez pas de documenter pourquoi.
Voir constructeur de copie et surcharge de fonctions.
3.10.Contrôle d'accès
Privatisez les données membres et fournissez des fonctions d'accès associées, telles que la définition de la variable foo_ et de la fonction de valeur foo(), ainsi que la fonction d'affectation set_foo().
La définition des fonctions d'accès est généralement intégrée dans le fichier d'en-tête.
Voir héritage et dénomination des fonctions.
3.11.Ordonnance de déclaration
Utilisez un ordre de déclaration spécifique dans une classe : public : avant private :, fonctions membres avant les données membres (variables).
L'ordre de définition est le suivant : public :, protected :, private :. Si cette partie n'est pas disponible, ignorez-la.
Au sein de chaque bloc, l'ordre de déclaration est généralement le suivant :
- typesdefs et énumérations ;
- constante;
- Constructeur;
- destructeur;
- Fonctions membres, y compris les fonctions membres statiques ;
- Données membres, y compris les données membres statiques.
La macro DISALLOW_COPY_AND_ASSIGN est placée après le bloc private: comme dernière partie de la classe. Voir constructeur de copie.
La définition des fonctions dans le fichier .cc doit être aussi cohérente que possible avec l'ordre de déclaration.
N'incorporez pas de fonctions volumineuses dans la définition de classe. Habituellement, seules les fonctions courtes qui n'ont pas de signification particulière ni d'exigences de performances élevées sont définies comme fonctions en ligne. Pour plus de détails, reportez-vous aux fonctions en ligne dans le premier article de la traduction.
3.12. Écrire des fonctions courtes
Préférez les fonctions courtes et concises.
Les fonctions longues sont parfois appropriées, il n'y a donc pas de limite stricte sur la longueur des fonctions. Si la fonction dépasse 40 lignes, pensez à la diviser sans affecter la structure du programme.
Même si une fonction longue fonctionne très bien désormais, une fois que quelqu'un la modifie, de nouveaux problèmes peuvent surgir, voire conduire à des bugs difficiles à trouver. Gardez les fonctions aussi courtes et simples que possible pour permettre aux autres de lire et de modifier plus facilement le code.
Au fur et à mesure que vous travaillez sur votre code, vous pouvez trouver des fonctions longues et complexes. N'ayez pas peur de modifier le code existant : s'il s'avère difficile à utiliser, à déboguer, ou si vous devez en utiliser une petite partie, envisagez de la diviser en plus petites , des pièces plus maniables, plusieurs fonctions.
3.13.Résumé
- Ne faites pas trop d’initialisations logiquement liées dans le constructeur ;
- Le constructeur par défaut fourni par le compilateur n'initialise pas les variables.Si d'autres constructeurs sont définis, le compilateur ne les fournira plus et le codeur doit fournir lui-même un constructeur par défaut ;
- Pour éviter une conversion implicite, le constructeur à paramètre unique doit être déclaré explicite ;
- Afin d'éviter les abus des constructeurs de copie, des opérations d'affectation et de génération automatique par le compilateur, vous pouvez actuellement les déclarer comme privés et n'avez pas besoin de les implémenter ;
- Utilisez struct uniquement comme collection de données ;
- Composition > Héritage d'implémentation > Héritage d'interface > Héritage privé, les fonctions virtuelles surchargées par des sous-classes doivent également déclarer le mot-clé virtual, bien que le compilateur ne le permette pas ;
- Évitez d'utiliser l'héritage multiple.Lors de son utilisation, à l'exception d'une classe de base qui contient l'implémentation, les autres classes de base sont de pures interfaces ;
- Le nom de la classe d'interface est suivi du suffixe Interface. À l'exception des destructeurs virtuels implémentés et des fonctions membres statiques, les autres sont de pures fonctions virtuelles. Il ne définit pas de données membres non statiques et ne fournit pas de constructeurs. S'il est fourni, déclarez-le comme protégé ;
- Pour réduire la complexité, essayez de ne pas surcharger les opérateurs et fournissez de la documentation lorsqu'ils sont utilisés dans des modèles et des classes standard ;
- Les fonctions d'accès sont généralement intégrées dans les fichiers d'en-tête ;
- Ordre de déclaration : public->protégé->privé ;
- Le corps fonctionnel doit être aussi court et compact que possible, avec une seule fonction.
4. Expérience de Google
Google dispose de nombreuses techniques et fonctions auto-implémentées qui rendent le code C++ plus robuste, ainsi que de façons d'utiliser le C++ qui sont différentes d'ailleurs.
4.1.Pointeurs intelligents
Si vous avez vraiment besoin d'utiliser des pointeurs intelligents, scoped_ptr est entièrement capable. Dans des cas très particuliers, comme pour les objets dans des conteneurs STL, vous ne devez utiliser que std::tr1::shared_ptr et ne jamais utiliser auto_ptr sous aucun prétexte.
Les pointeurs « intelligents » ressemblent à des pointeurs, mais sont en réalité des objets dotés d'une sémantique supplémentaire. En prenant scoped_ptr comme exemple, lorsque scoped_ptr est détruit, l'objet vers lequel il pointe est supprimé. La même chose est vraie pour shared_ptr, et shared_ptr implémente le comptage de références afin que le pointeur ne soit supprimé que lorsque le dernier objet vers lequel il pointe est détruit.
De manière générale, nous avons tendance à concevoir du code avec une affiliation d'objet claire. L'affiliation d'objet la plus claire consiste à utiliser l'objet directement comme champ ou variable locale sans utiliser de pointeurs du tout. L'autre extrême est que le pointeur compté par référence n'appartient à aucun objet. Le problème avec cette conception est qu'elle peut facilement conduire à des références circulaires ou à d'autres conditions étranges qui empêchent la suppression de l'objet, et même les opérations atomiques seront très ralentir lors de chaque copie ou affectation.
Bien que cela ne soit pas recommandé, il arrive parfois qu’un pointeur à références constitue la solution la plus simple et la plus efficace.
4.2.CPPlint
Utilisez cpplint.py pour détecter les erreurs de style.
Cpplint.py est un outil qui lit les fichiers sources et identifie les erreurs de style. Bien qu’il ne soit pas parfait et présente de nombreux avantages et inconvénients, il reste un outil utile. Les messages d'erreur actifs peuvent être ignorés en plaçant //NOLINT à la fin de la ligne.
Certains projets sont accompagnés d'instructions sur la façon d'exécuter cpplint.py à partir des outils du projet. Sinon, vous pouvez le télécharger séparément.
Note du traducteur : Il semble que ce que Google appelle la différence est d'essayer d'éviter d'utiliser des pointeurs intelligents :D, d'essayer d'être localisé lorsque vous les utilisez et de donner la priorité à la sécurité.
5. Autres fonctionnalités C++
5.1.Arguments de référence
Les paramètres passés par référence doivent donc être ajoutés avec const.
Définition : En langage C, si une fonction doit modifier la valeur d'une variable, le paramètre formel doit être un pointeur, comme int foo(int *pval). En C++, les fonctions peuvent également déclarer des paramètres de référence : int foo(int &val).
Avantages : Définir des paramètres formels comme références évite le code laid comme (*pval)++. C'est également nécessaire pour des applications telles que les constructeurs de copie, et contrairement aux pointeurs, il n'accepte pas les pointeurs nuls NULL.
Inconvénients : facilement mal compris, car les références sont syntaxiquement des valeurs mais ont une sémantique de pointeur.
Conclusion :
dans la liste des paramètres de la fonction, toutes les références doivent être const :
void Foo(const string &in, string out);
En fait, il s'agit d'une convention stricte : les paramètres d'entrée sont des valeurs ou des références constantes, et les paramètres de sortie sont pointeurs ; les paramètres d’entrée peuvent être des pointeurs constants, mais les paramètres de référence non const ne peuvent pas être utilisés.
Pour souligner que les paramètres ne sont pas copiés et doivent exister tout au long de la vie de l'objet, des pointeurs constants peuvent être utilisés. Il est préférable de les préciser dans les commentaires. Les adaptateurs STL tels que bind2nd et mem_fun n'acceptent pas les paramètres de référence, auquel cas la fonction doit également être déclarée avec des paramètres de pointeur.
5.2. Surcharge de fonctions
N'utilisez des fonctions surchargées (y compris les constructeurs) que lorsque les types de paramètres d'entrée sont différents et que les fonctions sont identiques. N'utilisez pas la surcharge de fonctions pour imiter les paramètres de fonction par défaut.
Définition : vous pouvez définir un type de paramètre de fonction comme const string& et définir son type de fonction surchargé comme const char .
class MaClasse { public : void Analyse (const string &text);
void Analyze(const char *text, size_t textlen);
};
Avantages : en surchargeant les fonctions du même nom avec des paramètres différents, le code est plus intuitif, le code basé sur un modèle doit être surchargé et cela apporte du confort aux visiteurs.
Inconvénients : une des raisons pour limiter l'utilisation de la surcharge est qu'il est difficile de déterminer quelle fonction est appelée lors d'un appel spécifique. Une autre raison est que lorsqu'une classe dérivée ne surcharge que certaines variables de la fonction, de nombreuses personnes seront confuses quant à la sémantique de l'héritage. . De plus, lors de la lecture du code client de la bibliothèque, les paramètres de fonction par défaut provoquent une confusion inutile.
Conclusion : si vous souhaitez surcharger une fonction, envisagez de laisser le nom de la fonction contenir des informations sur les paramètres, par exemple, utilisez AppendString(), AppendInt() au lieu de Append().
5.3.Arguments par défaut
Désactivez les fonctions utilisant les paramètres par défaut.
Avantages : une fonction est souvent utilisée avec un grand nombre de valeurs par défaut, et ces valeurs sont parfois remplacées. Les paramètres par défaut offrent la commodité de définir moins de fonctions pour des exceptions rarement impliquées.
Inconvénients : les utilisateurs examinent souvent le code existant pour déterminer comment utiliser une API. Les paramètres par défaut rendent difficile le copier-coller du code précédent pour présenter tous les paramètres. Cela peut entraîner des problèmes majeurs lorsque les paramètres par défaut ne s'appliquent pas au nouveau code.
Conclusion : tous les paramètres doivent être clairement spécifiés, obligeant les programmeurs à prendre en compte l'API et les valeurs de chaque paramètre transmis, et à éviter d'utiliser des paramètres par défaut qui peuvent ne pas être connus des programmeurs.
5.4. Tableaux de longueur variable et alloca()
Les tableaux de longueur variable et alloca() sont interdits.
Avantages : Les tableaux de longueur variable ont une syntaxe naturelle, et les tableaux de longueur variable et alloca() sont également très efficaces.
Inconvénients : les tableaux de longueur variable et alloca() ne font pas partie du C++ standard et, plus important encore, ils allouent des tailles en fonction des données sur la pile (stack), ce qui peut entraîner des fuites de mémoire difficiles à trouver : "Il fonctionne correctement sur ma machine, mais elle est morte inexplicablement lorsqu'elle est arrivée dans le produit.
Conclusion : utilisez un allocateur sûr tel que scoped_ptr/scoped_array.
5.5.Amis
L'utilisation raisonnable des classes amies et des fonctions amies est autorisée.
Les amis sont généralement définis dans le même fichier pour empêcher les lecteurs d'accéder à d'autres fichiers pour trouver leur utilisation comme membres privés d'une classe. Un endroit où les amis sont souvent utilisés est de déclarer FooBuilder comme ami de Foo, afin que FooBuilder puisse construire correctement l'état interne de Foo sans exposer cet état. Dans certains cas, il est pratique de déclarer une classe de test unitaire comme amie de la classe testée.
Les amis étendent (mais ne brisent pas) les limites d'encapsulation d'une classe. Lorsque vous souhaitez autoriser uniquement une autre classe à accéder à un membre, il est généralement préférable d'utiliser des amis plutôt que de le déclarer public. Bien entendu, la plupart des classes ne devraient proposer que des membres publics avec lesquels interagir.
5.6.Exceptions
N'utilisez pas d'exceptions C++.
avantage:
- Les exceptions permettent aux applications de niveau supérieur de décider comment gérer les échecs « impossibles » qui se produisent dans les fonctions imbriquées sous-jacentes, contrairement aux enregistrements de codes d'erreur qui sont aussi obscurs et alambiqués que possible ;
- Utilisée dans de nombreux autres langages modernes, l'introduction d'exceptions rend le C++ plus compatible avec Python, Java et d'autres langages similaires au C++ ;
- De nombreuses bibliothèques tierces C++ utilisent des exceptions, et leur désactivation rendra difficile leur combinaison ;
- Les exceptions sont la seule solution à l'échec du constructeur. Bien que les exceptions puissent être simulées via la fonction factory ou la méthode Init(), elles nécessitent respectivement une allocation de tas ou un nouvel état « illégal » ;
- Les exceptions sont vraiment utiles dans un cadre de test.
défaut: - Lors de l'ajout d'une instruction throw à une fonction existante, tous les sites d'appel doivent être vérifiés, et même s'ils disposent au moins d'une protection de sécurité de base contre les exceptions, ou si le programme se termine normalement, l'exception ne peut jamais être interceptée. Par exemple : si f() appelle g() appelle h(), h lève une exception interceptée par f, g doit faire attention à éviter un nettoyage incomplet ;
- En termes simples, les exceptions empêcheront le flux de contrôle du programme (flux de contrôle) d'être déterminé en examinant le code : la fonction peut revenir à un endroit incertain, ce qui rend la gestion du code et le débogage difficiles. Bien sûr, vous pouvez spécifier quand , où et comment l'utiliser. Les exceptions sont utilisées pour minimiser les frais généraux, mais imposent aux développeurs la tâche de maîtriser ces réglementations ;
- La sécurité des exceptions nécessite RAII et différentes pratiques de codage. Écrire facilement et correctement du code protégé contre les exceptions nécessite beaucoup de support. Autoriser les exceptions ;
- L'ajout d'exceptions augmentera la taille du code d'exécution binaire, augmentera le temps de compilation (peut ne pas avoir beaucoup d'impact) et peut également augmenter la pression sur l'espace d'adressage ;
- L'utilité des exceptions peut inciter les développeurs à lever des exceptions à des moments inappropriés ou à récupérer des exceptions dans des endroits dangereux, par exemple, où une entrée illégale de l'utilisateur peut entraîner la levée d'une exception. Autoriser les exceptions rendrait un guide de style de programmation comme celui-ci beaucoup plus long.
Conclusion :
En apparence, les avantages de l'utilisation d'exceptions l'emportent sur les inconvénients, en particulier dans les nouveaux projets. Cependant, pour le code existant, l'introduction d'exceptions impliquera tout le code dépendant. Si l'utilisation d'exceptions est autorisée dans de nouveaux projets, cela sera également problématique lors de l'intégration avec du code qui n'utilisait pas d'exceptions auparavant. Étant donné que la plupart du code C++ existant de Google n'a pas de gestion des exceptions, l'introduction d'un nouveau code avec gestion des exceptions est assez difficile.
Étant donné que le code existant de Google n'accepte pas les exceptions, l'utilisation d'exceptions dans le code existant coûte plus cher que leur utilisation dans de nouveaux projets. Le processus de migration sera plus lent et sujet aux erreurs. Nous ne pensons pas non plus que les alternatives valides aux exceptions, telles que les codes d'erreur, les assertions, etc., soient vraiment lourdes.
Nous ne nous opposons pas au recours aux exceptions pour des raisons philosophiques ou morales, mais pour des raisons pratiques. Parce que nous espérons utiliser des projets open source sur Google, mais que l'utilisation d'exceptions dans le projet entraînera des inconvénients, car nous recommandons également de ne pas utiliser d'exceptions dans les projets open source sur Google. Il est évidemment irréaliste si nous devons démolir ces projets et commencer fini. .
Il existe une exception à cela pour le code Windows (attendez le dernier message :D).
Note du traducteur : La gestion des exceptions ne peut évidemment pas être expliquée clairement en quelques phrases. Prenons l'exemple du constructeur. De nombreux livres en C++ mentionnent que seules les exceptions peuvent être gérées lorsque la construction échoue. Google interdit l'utilisation d'exceptions. pour votre propre commodité. Pour parler franchement, cela est simplement basé sur le coût de la gestion du logiciel. En utilisation réelle, vous prenez toujours votre propre décision.
5.7. Informations sur le type d'exécution (RTTI)
Nous interdisons l’utilisation de RTTI.
Définition : RTTI permet aux programmeurs d'identifier le type d'objets de classe C++ au moment de l'exécution.
Avantages : RTTI est très utile dans certains tests unitaires, comme lors du test des classes d'usine pour vérifier si un objet nouvellement créé est du type dynamique attendu.
Rarement utilisé sauf pour des tests.
Inconvénients : l'identification des types au moment de l'exécution signifie qu'il y a un problème avec la conception elle-même. Si vous devez déterminer le type d'un objet au moment de l'exécution, cela signifie généralement que vous devez repenser la conception de votre classe.
Conclusion : n'utilisez pas RTTI sauf pour les tests unitaires. Si vous constatez que le code que vous devez écrire se comporte différemment selon le type d'objet, envisagez une autre façon d'identifier le type d'objet.
Les fonctions virtuelles peuvent exécuter différents codes selon le type de sous-classe, et le travail est laissé à l'objet lui-même.
Si le travail est effectué dans du code en dehors de l'objet, envisagez un schéma de répartition double, tel que le modèle Visiteur, qui facilite la détermination du type de classe en dehors de l'objet lui-même.
Si vous pensez que vous ne pouvez pas maîtriser la méthode ci-dessus, vous pouvez utiliser RTTI, mais réfléchissez-y à deux fois et n'implémentez pas manuellement une solution qui ressemble à RTTI (solution de contournement de type RTTI). Nous sommes opposés à l'utilisation de RTTI, et nous sommes également opposés à l'héritage apparemment de classe avec des étiquettes de type.
5.8. Conversion de type (Casting)
Utilisez des conversions de type C++ telles que static_cast<>(). N'utilisez pas int y = (int)x ou int y = int(x).
Définition : C++ introduit différents types d'opérations de conversion de types qui sont différents du C.
Avantages : Le problème avec la conversion de type en langage C est que le fonctionnement de Shandong Haihua est relativement ambigu : parfois il effectue une conversion forcée (comme (int) 3.5), parfois il effectue une conversion de type (comme (int) "bonjour " "). De plus, la recherche de conversion de type en C++ est plus simple et plus attrayante.
Inconvénients : La syntaxe est méchante.
Conclusion : utilisez le style C++ plutôt que les conversions de style C.
- static_cast : similaire à la conversion de style C, il peut effectuer une coercition de valeur ou une conversion ascendante explicite d'un pointeur d'une classe parent vers une sous-classe ;
- const_cast : supprime les attributs const ;
- reinterpret_cast : conversion dangereuse entre les types de pointeurs et les pointeurs entiers ou autres, utilisez-la uniquement lorsque vous savez tout ce que vous faites ;
- Dynamic_cast : Ne l'utilisez pas sauf pour les tests. À l'exception des tests unitaires, si vous devez déterminer les informations de type au moment de l'exécution, la conception est défectueuse (voir RTTI).
5.9. Flux
Utilisez uniquement les flux lors de la connexion.
Définition : les flux remplacent printf() et scanf().
Avantages : avec les flux, vous n'avez pas besoin de vous soucier du type de l'objet lors de la sortie, et vous n'avez pas à vous soucier du fait que la chaîne de format ne correspond pas à la liste de paramètres (bien que ce problème n'existe pas lors de l'utilisation de printf dans gcc ). Lors de l'ouverture et de la fermeture du fichier correspondant, le flux peut être construit et détruit automatiquement.
Inconvénients : Les flux rendent difficile l'exécution de fonctions fonctionnelles telles que pread(). Si vous n'utilisez pas de fonctions telles que printf mais utilisez des flux, il est difficile de faire fonctionner le format (en particulier la chaîne de format couramment utilisée %.*s). Streams ne prend pas en charge les caractères. Réorganisation des opérateurs de chaîne (%1s), ce qui est utile pour l'internationalisation.
Conclusion : n'utilisez pas de flux sauf si l'interface de journalisation l'exige, utilisez plutôt printf ou similaire. Il y a de nombreux avantages et inconvénients à utiliser des flux, la cohérence du code l'emporte sur tout, n'utilisez pas de flux dans votre code.
Discussion approfondie :
il y a un débat sur cette règle, et voici les raisons les plus profondes. Rappelez-vous le principe d'unicité (Only One Way) : nous voulons utiliser un seul type d'E/S à tout moment pour rendre le code cohérent pour toutes les E/S. Par conséquent, nous ne voulons pas que l'utilisateur décide s'il doit utiliser streams ou printf + read/write, nous devons décider quelle méthode utiliser. La raison pour laquelle les journaux constituent une exception est que les flux sont bien adaptés à cela, et il y a des raisons historiques à cela.
Les partisans du streaming affirment que le streaming est le choix évident, mais leur argument n’est ni clair ni solide : tous les avantages qu’ils soulignent sont aussi ses inconvénients. Le plus grand avantage des flux est que vous n'avez pas besoin de vous soucier du type de l'objet de sortie lors de la sortie. C'est un point fort, mais aussi un inconvénient : il est facile d'utiliser le mauvais type et le compilateur ne vous alertera pas. . Un type d'erreur qui se produit facilement lors de l'utilisation de flux est :
cout << this; // Imprime l'adresse
cout << *this; // Imprime le contenu.
Le compilateur ne signalera pas d'erreur car << est surchargé. En raison de ceci nous L'utilisation de la surcharge d'opérateurs est déconseillée.
Certaines personnes disent que le formatage de printf est laid et illisible, mais les flux ne sont pas vraiment meilleurs. Jetez un œil aux deux morceaux de code suivants. Lequel est le plus lisible ?
cerr << "Erreur de connexion à '" << foo->bar()->hostname.first
<< ":" << foo->bar()->hostname.second << " : " << strerror(errno );
fprintf(stderr, "Erreur de connexion à '%s:%u: %s",
foo->bar()->hostname.first, foo->bar()->hostname.second,
strerror(errno));
vous Vous pourriez dire : « Il serait préférable d'encapsuler le flux. » C'est bien ici, mais qu'en est-il des autres endroits ? Et n'oubliez pas que l'objectif est de rendre le langage aussi petit que possible, et non d'ajouter de nouvelles choses que d'autres doivent apprendre.
Chaque méthode a ses propres avantages et inconvénients. " Il n'y a pas de meilleur, seulement du meilleur. " Le dogme simpliste nous avertit que nous devons en choisir une. La décision finale majoritaire est printf + read/write.
5.10. Pré-incrémentation et pré-décrémentation
Utilisez la forme préfixée (++i) des opérateurs d'incrémentation et de décrémentation pour les itérateurs et autres objets de modèle.
Définition : Lorsque la valeur de l'expression n'est pas utilisée après que la variable est incrémentée (++i ou i++) ou décrémentée (–i ou i–), il est nécessaire de déterminer s'il faut utiliser le préfixe ou le suffixe auto.
Avantages : Quelle que soit la valeur de retour, le pré-incrémentation (++i) est généralement plus efficace que le post-incrémentation (–i), car le post-incrémentation et la décrémentation nécessitent une copie de la valeur i de l'expression. , si i est un itérateur ou un autre type non numérique, le coût de la copie est relativement élevé. Puisque les deux méthodes d’auto-incrémentation ont la même action, pourquoi ne pas simplement utiliser directement la pré-incrémentation ?
Inconvénients : En langage C, lorsque la valeur d'une expression n'est pas utilisée, l'approche traditionnelle consiste à utiliser le post-incrémentation, notamment dans les boucles for. Certains pensent que le post-incrémentation est plus facile à comprendre car il s'apparente au langage naturel. (i) vient avant le verbe prédicat (++).
Conclusion : pour les valeurs simples (non-objets), cela n'a pas d'importance dans les deux cas. Pour les itérateurs et les types de modèles, utilisez un incrément préfixé (décrément).
5.11.Utilisation de const
Nous vous recommandons fortement d'utiliser const dans la mesure du possible.
Définition : Ajoutez le mot-clé const avant la variable ou le paramètre déclaré pour indiquer que la valeur de la variable ne peut pas être modifiée (comme const int foo). Ajoutez la qualification const à la fonction dans la classe pour indiquer que la fonction ne modifiera pas l'état de la variable membre de la classe (telle que class Foo { int Bar(char c) const; };).
Avantages : Il est plus facile pour les gens de comprendre comment les variables sont utilisées, et les éditeurs peuvent effectuer une meilleure vérification de type et une meilleure génération de code. Les gens se sentent plus en confiance pour écrire du code correct car ils savent que les fonctions qu'ils appellent sont limitées dans la mesure où ils peuvent ou non modifier les valeurs des variables. Même dans la programmation multithread sans verrouillage, les utilisateurs savent quelles fonctions sont sûres.
Inconvénients : si vous transmettez une variable const à une fonction, le prototype de la fonction doit également être const (sinon la variable nécessite une conversion de type const_cast), ce qui est particulièrement gênant lors de l'appel de fonctions de bibliothèque.
Conclusion : les variables const, les données membres, les fonctions et les paramètres ajoutent une couche de protection à la détection de type au moment de la compilation et permettent de mieux détecter les erreurs le plus tôt possible. Par conséquent, nous vous recommandons fortement d’utiliser const autant que possible :
- Si la fonction ne modifie pas les paramètres de référence ou de type pointeur passés, ces paramètres doivent être const ;
- Déclarez les fonctions comme const autant que possible. Les fonctions d'accès doivent toujours être const. Les autres fonctions qui ne modifient aucune donnée membre doivent également être const. N'appelez pas de fonctions non const et ne renvoyez pas de pointeurs ou de références non const aux données membres ;
- Si une donnée membre ne change pas après la construction de l'objet, elle peut être définie comme const.
Cependant, n'abusez pas de const. Par exemple, const int * const * const x; est un peu excessif. Même s'il décrit x avec précision, vous pouvez en fait l'écrire sous la forme const int** x.
Le mot-clé mutable peut être utilisé, mais il n'est pas sûr en multi-threading. Lorsque vous l'utilisez, vous devez d'abord prendre en compte la sécurité des threads.
position const :
Certaines personnes préfèrent la forme int const foo à const int foo. Ils pensent que la première est plus cohérente et donc plus lisible : elle suit le principe selon lequel const est toujours situé après l'objet qu'il décrit (int). Cependant, le principe de cohérence ne s'applique pas ici, et l'autorité de « ne pas abuser » contrecarre une utilisation cohérente. Mettre const en premier est plus facile à lire, car en langage naturel, l'adjectif (const) précède le nom (int).
Cela signifie que nous préconisons de mettre const en premier, pas une exigence, mais nous devons prendre en compte la cohérence du code !
5.12.Types entiers
Parmi les types entiers intégrés en C++, le seul utilisé est int. Si des variables de tailles différentes sont nécessaires dans le programme, vous pouvez utiliser le type entier à largeur précise dans <stdint.h>, tel que int16_t.
Définition : C++ ne spécifie pas la taille des types entiers. En général, les gens pensent que short est de 16 bits, int de 32 bits, long de 32 bits et long long de 64 bits.
Avantages : Maintient la cohérence des déclarations.
Inconvénients : les tailles entières en C++ varient selon les compilateurs et les architectures.
Conclusion :
<stdint.h> définit int16_t, uint32_t, int64_t et d'autres types entiers. Lorsque vous devez déterminer la taille du type entier, vous pouvez les utiliser à la place de short, unsigned long long, etc. Parmi les types entiers C, seul int est utilisé. Le cas échéant, il est recommandé d'utiliser des types standards tels que size_t et ptrdiff_t.
Le plus couramment utilisé est celui pour les entiers, qui ne sont généralement pas trop grands, comme le comptage de boucles, un int ordinaire peut être utilisé. Vous pouvez considérer int comme au moins 32 bits, mais ne pensez pas qu'il dépassera 32 bits. Si vous avez besoin d'un entier de 64 bits, vous pouvez utiliser int64_t ou uint64_t.
Pour les grands entiers, utilisez int64_t.
N'utilisez pas d'entiers non signés tels que uint32_t, sauf si vous représentez un modèle binaire plutôt qu'une valeur. N'utilisez pas de types non signés même si la valeur ne sera pas négative, utilisez des assertions pour protéger les données.
Types entiers non signés :
certaines personnes, y compris certains auteurs de manuels, recommandent d'utiliser des types non signés pour représenter des nombres non négatifs, où le type indique la forme de la valeur numérique. Cependant, en C, cet avantage est contrebalancé par les bugs qu’il provoque. Jetez un œil :
for (unsigned int i = foo.Length()-1; i >= 0; --i) …
Le code ci-dessus ne se termine jamais ! Parfois, gcc trouvera le bug et vous alertera, mais généralement pas. Des bogues similaires apparaîtront également lors de la comparaison de variables qualifiées et de variables non signées, principalement parce que le mécanisme de promotion de type C (schéma de promotion de type, la relation de conversion de promotion entre divers types intégrés dans le langage C) entraînera un comportement inattendu des types non signés.
Par conséquent, utilisez des assertions pour déclarer les variables comme étant non négatives et n'utilisez pas de types non signés.
Portabilité 5.13.64 bits
En principe, le code devrait être plus convivial dans les systèmes 64 bits et 32 bits, en particulier pour la sortie, la comparaison et l'alignement de la structure :
- Certains types spécifiés par printf() ne sont pas très portables sur les systèmes 32 bits et 64 bits, et la norme C99 définit certains formats portables. Malheureusement, MSVC 7.1 ne les prend pas tous en charge et certains manquent dans la norme. Donc parfois nous devons définir nous-mêmes la version laide (en utilisant le style standard qui inclut le fichier inttypes.h) :
// macros printf pour size_t, dans le style de inttypes.h
#ifdef _LP64
#define __PRIS_PREFIX "z"
#else
#define __PRIS_PREFIX
#endif
// Utilisez ces macros après un % dans une chaîne au format printf
// pour obtenir un comportement 32/64 bits correct, comme ceci :
// size_t size = records.size();
// printf("%"PRIuS"\n", taille);
#define PRIdS __PRIS_PREFIX "d"
#define PRIxS __PRIS_PREFIX "x"
#define PRIuS __PRIS_PREFIX "u"
#define PRIXS __PRIS_PREFIX "X"
#define PRIoS PRIS_PREFIX "o"
type ne pas utiliser utiliser de remarques
void (ou autre type de pointeur) %lx % p
int64_t %qd, %lld %"PRId64"
uint64_t %qu, %llu, %llx %"PRIu64", %"PRIx64"
size_t %u %"PRIuS", %"PRIxS" Spécification C99 %zu
ptrdiff_t %d % " PRIdS" C99 spécifie %zd.
Notez que la macro PRI sera développée en une chaîne indépendante par le compilateur. Par conséquent, si vous utilisez une chaîne de format non const, vous devez insérer la valeur de la macro dans le format au lieu de le nom de la macro. Il en va de même lors de l'utilisation de la macro PRI* La longueur et d'autres informations peuvent être spécifiées après %. Par exemple, printf("x = %30"PRIuS"\n", x) sera étendu à printf("x = %30" "u" "\n", x) sur Linux 32 bits, et le compilateur gérera est printf("x = %30u\n", x).
2) N'oubliez pas sizeof(void *) != sizeof(int), si vous avez besoin d'un entier de la taille d'un pointeur, utilisez intptr_t.
3) Des précautions doivent être prises lors de l'alignement des structures, en particulier pour les structures stockées sur disque. Sur les systèmes 64 bits, toute classe/structure avec des membres int64_t/uint64_t sera traitée par défaut comme un alignement sur 8 octets. Si le code 32 bits et 64 bits partage des structures sur le disque, vous devez vous assurer que les structures sont alignées de manière cohérente sur les deux architectures. La plupart des compilateurs proposent un moyen d'ajuster l'alignement des structures. __attribute ((packed)) peut être utilisé dans gcc , et MSVC fournit #pragma pack() et __declspec(align()).
4) Utilisez LL ou ULL comme suffixe lors de la création de constantes 64 bits, telles que :
int64_t my_value = 0x123456789LL ;
uint64_t my_mask = 3ULL << 48 ;
5) Si vous avez vraiment besoin que les systèmes 32 bits et 64 bits aient des codes différents, vous pouvez précéder l'utilisation de la variable de code. (Essayez de ne pas faire cela et essayez de localiser les modifications lors de son utilisation).
5.14. Macros du préprocesseur
Soyez prudent lorsque vous utilisez des macros et essayez plutôt d’utiliser des fonctions, des énumérations et des constantes en ligne.
Les macros signifient que vous et le compilateur voyez le code différemment, ce qui peut entraîner un comportement inattendu, surtout si la macro existe dans la portée globale.
Heureusement, en C++, les macros ne sont pas aussi nécessaires qu'en C. Le code critique pour l'efficacité des macros (code critique pour les performances) peut être remplacé par des fonctions en ligne ; les constantes stockées dans les macros peuvent être remplacées par des variables const ; les macros "abrégent" les noms de variables longs peuvent être remplacés par des références ; utiliser des macros pour la compilation conditionnelle, ceci. .., il est préférable de ne pas le faire. Cela rendrait les tests plus pénibles (à l'exception de #define pour empêcher la réinclusion des fichiers d'en-tête).
Les macros peuvent faire certaines choses qui ne peuvent pas être réalisées par d'autres technologies. Dans certaines bibliothèques de code (en particulier dans les bibliothèques de bas niveau), vous pouvez voir certaines fonctionnalités des macros (telles que la chaîne (Note du traducteur, utilisez #), la concaténation, la note du traducteur, utilisez ##), etc.). Mais avant de l'utiliser, réfléchissez bien si vous pouvez obtenir le même effet sans utiliser de macros.
Note du traducteur : pour les applications avancées des macros, veuillez vous référer à "Applications avancées des macros en langage C".
Les modèles d'utilisation donnés ci-dessous peuvent éviter certains problèmes lors de l'utilisation de macros et sont à titre de référence lors de l'utilisation de macros :
- Ne définissez pas de macros dans les fichiers .h ;
- #define correctement avant utilisation, #undef correctement après utilisation ;
- Ne vous contentez pas de #undef une macro existante, choisissez un nom qui n'entrera pas en conflit ;
- N'utilisez pas de macros qui peuvent provoquer des constructions C++ déséquilibrées (Note du traducteur), documentez au moins leur comportement.
5.15.0 et NULL (0 et NULL)
Utilisez 0 pour les entiers, 0,0 pour les nombres réels, NULL pour les pointeurs et « \0 » pour les caractères (chaînes).
Il ne fait aucun doute que 0 est utilisé pour les nombres entiers et 0,0 pour les nombres réels.
Pour les pointeurs (valeurs d'adresse), qu'il s'agisse d'utiliser 0 ou NULL, Bjarne Stroustrup recommande d'utiliser le 0 d'origine. Nous recommandons d'utiliser NULL, qui ressemble à un pointeur. En fait, certains compilateurs C++ (comme gcc 4.1.0) fournissent spécifiquement le la définition de NULL peut donner des avertissements utiles, surtout lorsque sizeof(NULL) et sizeof(0) ne sont pas égaux.
Utilisez '\0' pour les caractères (chaînes), qui ont non seulement le type correct mais également une bonne lisibilité.
5.16.taillede
Utilisez sizeof(varname) au lieu de sizeof(type) autant que possible.
Sizeof(varname) est utilisé car le code est automatiquement synchronisé lorsque le type de variable change. Dans certains cas, sizeof(type) peut avoir du sens, mais il doit être évité autant que possible. Si le type de variable change, il ne peut pas être synchronisé .
Données de structure ;
memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct));
5.17.Bibliothèque Boost (Boost)
Utilisez uniquement les bibliothèques approuvées dans Boost.
Définition : La bibliothèque Boost est une bibliothèque C++ open source gratuite, évaluée par des pairs, très populaire.
Avantages : la qualité du code Boost est généralement supérieure, la portabilité est bonne et comble de nombreuses lacunes dans la bibliothèque standard C++, telles que les traits de type, des classeurs plus complets, de meilleurs pointeurs intelligents et fournit également l'implémentation de TR1 (une extension de la bibliothèque standard). ).
Inconvénients : Certaines bibliothèques Boost favorisent une mauvaise lisibilité des pratiques de programmation, telles que la métaprogrammation et d'autres techniques de modèles avancées, et un style de programmation trop « fonctionnel ».
Conclusion : Afin d'offrir une meilleure lisibilité à ceux qui lisent et maintiennent le code, nous autorisons uniquement l'utilisation d'un sous-ensemble mature de fonctionnalités Boost. Actuellement, ces bibliothèques incluent :
- Paire compressée:boost/compressed_pair.hpp;
- Conteneur de pointeur : boost/ptr_container n'inclut pas ptr_array.hpp ni la sérialisation.
Nous envisagerons activement d'ajouter des fonctionnalités Boost dans la mesure du possible, afin que vous n'ayez pas à vous en tenir à cette règle.
5.18.Résumé
- Pour les pointeurs intelligents, la sécurité d'abord, la commodité ensuite, et être aussi localisé que possible (scoped_ptr) ;
- Le paramètre de référence est const, sinon le paramètre pointeur est utilisé ;
- L'utilisation de la surcharge de fonctions doit être claire et facile à lire ;
- Compte tenu de la facilité d'utilisation abusive, l'utilisation des paramètres de fonction par défaut est interdite (discutable) ;
- L'utilisation de tableaux de longueur variable est interdite ;
- Utilisation raisonnable des amis ;
- Afin de faciliter la gestion du code, le recours aux exceptions est interdit (discutable) ;
- Il est interdit d'utiliser RTTI, sous peine de repenser le code ;
- Utilisez la conversion de type de style C++ et n'utilisez pas Dynamic_cast sauf pour les tests unitaires ;
- Utilisez stream ou printf + read/write, c'est un problème ;
- La pré-incrémentation/diminution peut être utilisée mais la post-incrémentation/diminution n'est pas requise ;
- Utilisez const s'il peut être utilisé et préconisez d'abord const ;
- Utilisez des types entiers d'une certaine taille et n'utilisez pas de types non signés sauf les octets ;
- Lors du formatage de la sortie et de l'alignement de la structure, faites attention aux différences entre les systèmes 32 bits et 64 bits ;
- Essayez d'éviter d'utiliser des macros, sauf pour la stringification et la concaténation ;
- Utilisez 0 pour les entiers, 0,0 pour les nombres réels, NULL pour les pointeurs et « \0 » pour les caractères (chaînes) ;
- Utilisez sizeof(varname) au lieu de sizeof(type);
- Utilisez uniquement les bibliothèques approuvées dans Boost.
6. Convention de dénomination
La règle de cohérence la plus importante est la gestion des noms. Le style de dénomination peut directement déterminer si l'entité nommée est : un type, une variable, une fonction, une constante, une macro, etc., sans chercher de déclarations d'entité. Le moteur de correspondance de modèles dans notre cerveau s'appuie sur celles-ci. règles de dénomination.
Les règles de dénomination sont quelque peu arbitraires, mais la cohérence est plus importante que la dénomination en fonction de préférences personnelles, donc peu importe ce que vous pensez, les règles restent des règles.
6.1. Règles générales de dénomination
La dénomination des fonctions, des variables et des fichiers doit être descriptive et pas trop abrégée. Les types et les variables doivent être des noms, et les noms de fonctions peuvent utiliser des verbes "impératifs".
Comment nommer :
Donnez un nom descriptif autant que possible, n'économisez pas d'espace, il est plus important de laisser les autres comprendre votre code rapidement, bons choix de nommage : int
num_errors; // Bon.
int num_completed_connections; // Bon.
Utiliser noms vagues pour les noms laids, abréviation ou caractères aléatoires :
int n; // Mauvais - sans signification.
int nerr; // Mauvais - abréviation ambiguë.
int n_comp_conns; // Mauvais - abréviation ambiguë.
Les types et les noms de variables sont généralement des noms : comme FileOpener , num_erreurs.
Les noms de fonctions sont généralement directifs, comme OpenFile(), set_num_errors(). Les fonctions d'accès doivent être décrites plus en détail et doivent correspondre aux variables auxquelles elles accèdent.
Abréviations :
n'utilisez pas d'abréviations à moins qu'elles ne soient évidentes en dehors du projet, par exemple :
// Bon
// Ceux-ci affichent des noms propres sans abréviations.
int num_dns_connections; // La plupart des gens savent ce que signifie « DNS ».
int price_count_reader; / / OK, le prix compte. C'est logique.
// Mauvais!
// Les abréviations peuvent prêter à confusion ou être ambiguës en dehors d'un petit groupe.
int wgc_connections; // Seul votre groupe sait ce que cela signifie.
int pc_reader; // Beaucoup de choses peuvent être abrégées en « pc
»
. // Bien.
int erreur_cnt; // Mauvais.
6.2.Noms de fichiers
Le nom du fichier doit être entièrement en minuscules et peut contenir des traits de soulignement (_) ou des tirets (-), conformément à la convention du projet.
Nom de fichier acceptable :
my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
Les fichiers C++ se terminent par .cc et les fichiers d'en-tête se terminent par .h.
N'utilisez pas de noms de fichiers qui existent déjà sous /usr/include (Note du traducteur, pour UNIX, Linux et autres systèmes), tels que db.h.
Généralement, essayez de rendre le nom du fichier plus clair. http_server_logs.h est meilleur que logs.h. Lors de la définition d'une classe, le nom du fichier apparaît généralement par paires, telles que foo_bar.h et foo_bar.cc, correspondant à la classe FooBar.
Les fonctions en ligne doivent être placées dans le fichier .h. Si la fonction en ligne est courte, placez-la directement dans le fichier .h. Si le code est relativement long, il peut être placé dans un fichier se terminant par -inl.h. Pour les classes contenant beaucoup de code en ligne, il peut y avoir trois fichiers :
url_table.h // La déclaration de classe.
url_table.cc // La définition de la classe.
url_table-inl.h // Les fonctions en ligne qui incluent beaucoup de code.
Reportez-vous au première section du fichier Chapter-inl.h.
6.3.Noms des types
Chaque mot du nom du type commence par une lettre majuscule et ne contient pas de traits de soulignement : MyExcitingClass, MyExcitingEnum.
Tous les noms de types (classes, structures, typedefs, énumérations) utilisent la même convention, par exemple :
// classes et structures
class UrlTable { …
class UrlTableTester { …
struct UrlTableProperties { …
// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;
// énumération
enum UrlTableErrors { …
6.4. Noms des variables
Les noms de variables sont toujours en minuscules, les mots sont connectés par des traits de soulignement et les variables membres de la classe se terminent par des traits de soulignement, tels que my_exciting_local_variable, my_exciting_member_variable_.
Dénomination de variable ordinaire :
Exemple :
chaîne nom_table ; // OK - utilise un trait de soulignement.
chaîne nom_table ; // OK - tout en minuscules.
chaîne Nom_table ; // Mauvais - casse mixte.
Membres de données de classe :
les données membres de la structure peuvent être les mêmes comme les variables ordinaires, pas besoin de terminer par un trait de soulignement comme une classe :
struct UrlTableProperties { string name; int num_entries; } Pour une discussion sur les structures et les classes, veuillez vous référer à la section "Structures vs. Classes" dans le troisième article. Variables globales : il n'y a pas d'exigences particulières pour les variables globales. Utilisez-les avec parcimonie. Elles peuvent être préfixées par g_ ou d'autres signes faciles à distinguer des variables locales.
6.5. Noms de constantes
Préfixez le nom avec k : kDaysInAWeek.
Toutes les constantes de compilation (qu'elles soient locales, globales ou au sein d'une classe) restent légèrement différentes des autres variables, k est suivi d'un mot commençant par une majuscule : const int kDaysInAWeek = 7
;
6.6. Noms des fonctions
Les fonctions régulières (fonctions régulières, ndlr, ici par opposition aux fonctions spéciales telles que les fonctions d'accès) ont une casse mixte, tandis que les accesseurs et les mutateurs doivent correspondre au nom de la variable : MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable( ).
Fonction ordinaire :
Le nom de la fonction commence par une lettre majuscule, la première lettre de chaque mot est en majuscule et il n'y a pas de soulignement :
AddTableEntry()
DeleteUrl()
Fonction d'accès :
La fonction d'accès doit correspondre au nom de la variable accédée. Voici un extrait d'une variable d'instance num_entries_ Class :
class MyClass { public: … int num_entries() const { return num_entries_; } void set_num_entries(int num_entries) { num_entries_ = num_entries; }
private:
int num_entries_;
};
D'autres noms courts de fonctions en ligne peuvent également utiliser des lettres minuscules. Par exemple, appeler une telle fonction dans une boucle n'a même pas besoin de mettre sa valeur en cache, et la dénomination en minuscules est acceptable.
Note du traducteur : on peut voir à partir de ce point que le nom de la fonction en minuscule signifie qu'elle peut être utilisée directement en ligne.
6.7. Noms d'espace de noms
Le nom de l'espace de noms est entièrement en minuscules et est nommé en fonction du nom du projet et de la structure du répertoire : google_awesome_project.
Pour une discussion sur les espaces de noms et comment les nommer, reportez-vous à la Partie 2 : Espaces de noms.
6.8. Noms des enquêteurs
Les valeurs d'énumération doivent être en lettres majuscules, avec des mots séparés par des traits de soulignement : MY_EXCITING_ENUM_VALUE.
Le nom de l'énumération est de type, donc en casse mixte : UrlTableErrors.
enum UrlTableErrors { OK = 0, ERROR_OUT_OF_MEMORY, ERROR_MALFORMED_INPUT, } ;
6.9. Noms de macros
Vous n'allez pas utiliser de macros, n'est-ce pas ? Si utilisé, comme ceci : MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
Reportez-vous au quatrième chapitre des macros de prétraitement. Les macros ne sont généralement pas utilisées. Si elles sont absolument nécessaires, leurs noms doivent être en majuscules et soulignés comme les noms d'énumération : #define ROUND(x) … #define
PI_ROUNDED
3.0
6.10.Exceptions aux règles de dénomination
Lorsque vous nommez des objets similaires à des entités C/C++ existantes, vous pouvez vous référer aux conventions de dénomination existantes : nom de la fonction
bigopen() , reportez-vous à la définition du type open() uint typedef, structure ou classe bigpos , reportez-vous à pos sparse_hash_map Entités similaires STL ; reportez-vous à STL dénomination Constante conventionnelle LONGLONG_MAX , similaire à INT_MAX
6.11.Résumé
- Règle générale : n'abrégez pas à volonté. S'il est excusable d'écrire ChangeLocalValue sous la forme ChgLocVal, écrire ModifyPlayerName sous la forme MdfPlyNm est trop. À l'exception des noms de fonctions qui peuvent être des verbes de manière appropriée, essayez d'utiliser des noms clairs et faciles à comprendre pour les autres. noms 2. Macros
, Utiliser des majuscules + des traits de soulignement pour les énumérations, etc.; - Utilisez toutes les lettres minuscules + trait de soulignement pour les variables (y compris les variables membres de classe et de structure), les fichiers, les espaces de noms, les fonctions d'accès, etc. Les variables membres de classe se terminent par un trait de soulignement et les variables globales commencent par g_ ;
- Les fonctions ordinaires, les types (y compris les classes, les structures, les types d'énumération), les constantes, etc. utilisent une casse mixte, sans traits de soulignement ;
- Faites référence à des conventions de dénomination existantes ou similaires.
7. Commentaires
Bien que les commentaires soient pénibles à écrire, ils sont cruciaux pour garantir la lisibilité du code. Les règles suivantes décrivent quels commentaires doivent être faits et où ils doivent se trouver. Bien sûr, rappelez-vous que les commentaires sont effectivement importants, mais le meilleur code est auto-documenté. Il est bien préférable d'avoir des noms de types et de variables clairs plutôt que d'expliquer des noms ambigus par des commentaires.
Les commentaires sont écrits pour quelqu'un d'autre (la prochaine personne qui doit comprendre votre code), alors soyez sérieux, cette prochaine personne pourrait être vous !
7.1.Style de commentaire
Utilisez // ou /* / pour unifier.
// ou / */ peuvent être utilisés, // est simplement utilisé plus largement pour garantir l'uniformité dans la façon de commenter et dans le style du commentaire.
7.2. Commentaires sur le fichier
Ajoutez une mention de droit d'auteur au début de chaque fichier, suivie d'une description du contenu du fichier.
Mentions légales et informations sur l'auteur :
Chaque document contient les éléments suivants, dans l'ordre :
- Copyright (déclaration de copyright) : tel que Copyright 2008 Google Inc. ;
- Version de licence (licence passe-partout) : choisissez la version de licence appropriée pour le projet, telle que Apache 2.0, BSD, LGPL, GPL ;
- Auteur (ligne d'auteur) : Identifie l'auteur original du fichier.
Si vous apportez des modifications importantes à un fichier créé par quelqu'un d'autre, ajoutez vos informations aux informations sur l'auteur afin que les autres sachent qui contacter s'ils ont des questions sur le fichier.
Contenu du fichier :
après l'autorisation de droit d'auteur et les informations sur l'auteur de chaque fichier, un commentaire décrivant le contenu du fichier doit être fourni.
Habituellement, le fichier .h doit donner une brève description des fonctions et de l'utilisation de la classe déclarée, et le fichier .cc contient plus de détails d'implémentation ou de discussions sur les algorithmes. Si vous pensez que ces détails d'implémentation ou ces discussions sur les algorithmes sont utiles à la lecture, vous peut mettre .Les commentaires en cc sont placés en .h et indiquent en .cc que le document est en .h.
Ne copiez pas simplement les commentaires entre .h et .cc. Les commentaires copiés s'écarteront de leur signification réelle.
7.3. Commentaires de classe
Chaque définition de classe est accompagnée de commentaires décrivant la fonctionnalité et l'utilisation de la classe.
// Itère sur le contenu d'un GargantuanTable. Exemple d'utilisation :
// GargantuanTable_Iterator* iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter- >Next()) { // process(iter->key(), iter->value()); // } // delete iter; class GargantuanTable_Iterator { … }; Si vous pensez que cela a été décrit en détail sur en haut du fichier Class, si vous voulez simplement dire "voir le haut du fichier pour une description complète", vous devez ajouter quelques commentaires dans la classe. Si la classe a des hypothèses de synchronisation, documentez-les. Si les instances de cette classe sont accessibles par plusieurs threads, veillez à prêter attention à la documentation lorsque vous les utilisez.
7.4. Commentaires sur les fonctions
Les commentaires sur la déclaration de fonction décrivent la fonction et la définition de la fonction décrit l'implémentation de la fonction.
Déclaration de fonction :
Un commentaire est placé avant la déclaration pour décrire la fonction et son utilisation. Le commentaire utilise une description ("Ouvre le fichier") plutôt qu'une instruction ("Ouvrir le fichier") ; le commentaire sert uniquement à décrire la fonction plutôt que que de dire à la fonction quoi faire. Habituellement, les commentaires ne décrivent pas comment la fonction est implémentée, cela fait partie de la définition.
Le contenu de l'annotation lors de la déclaration de la fonction :
- entrées (entrée) et sorties (sortie) ;
- Pour les fonctions membres de la classe : si l'objet doit conserver les paramètres de référence pendant l'appel de la fonction et si ces paramètres seront libérés ;
- Si la fonction alloue de l'espace, celui-ci doit être libéré par l'appelant ;
- Si le paramètre peut être NULL ;
- L'utilisation de la fonction a-t-elle des implications en termes de performances ?
- 举例
如下:
Renvoie un itérateur pour cette table. Il est de la responsabilité du client
// de supprimer l'itérateur lorsqu'il en a terminé,
// et il ne doit pas utiliser l'itérateur une fois que l'objet GargantuanTable
// sur lequel l'itérateur a été créé a été supprimé.
//
// L'itérateur est initialement positionné en début de tableau.
//
// Cette méthode est équivalente à :
// Iterator* iter = table->NewIterator();
// iter->Recherche("");
// renvoie l'itérateur ;
// Si vous cherchez immédiatement un autre endroit dans l'
itérateur // renvoyé, il sera plus rapide d'utiliser NewIterator()
// et évitez la recherche supplémentaire.
Itérateur* GetIterator() const;
Mais n'ayez pas de commentaires inutiles, redondants ou évidents. Il n'est pas nécessaire d'ajouter « renvoie faux sinon » au commentaire suivant, car il est déjà implicite : //
Renvoie vrai si la table ne peut plus contenir d'entrées.
bool IsTableFull() ;
Lorsque vous commentez des constructeurs/destructeurs, n'oubliez pas que la personne qui lit le code sait ce qu'est le constructeur/destructeur, donc un commentaire comme « détruit cet objet » n'a aucun sens. Expliquez ce que le constructeur fait aux paramètres (par exemple, s'il est le propriétaire du pointeur) et ce que le destructeur nettoie. Si cela n'est pas pertinent, omettez simplement le commentaire. Il est normal de n'avoir aucun commentaire avant le destructeur.
Définition de la fonction :
lors de la définition de chaque fonction, des commentaires doivent être utilisés pour expliquer la fonction et les points de mise en œuvre, tels que le beau code utilisé, les brèves étapes de mise en œuvre, la raison d'une telle mise en œuvre et pourquoi la première moitié doit être verrouillée mais la seconde moitié ne le fait pas.
Ne copiez pas les commentaires directement à partir de la déclaration de fonction dans le fichier .h ou ailleurs. Il est acceptable de décrire brièvement ce que fait la fonction, mais l'accent doit être mis sur la façon de l'implémenter.
7.5. Commentaires sur les variables
Habituellement, le nom de la variable lui-même suffit à décrire le but de la variable. Dans certains cas, des commentaires supplémentaires sont nécessaires.
Membres de données de classe :
chaque membre de données de classe (également appelé variable d'instance ou variable membre) doit être commenté pour expliquer son objectif. Si la variable peut accepter des valeurs sentinelles telles que NULL ou -1, elle doit être expliquée, telle que : privé :
//
Garde une trace du nombre total d'entrées dans la table.
// Utilisé pour garantir que nous ne dépassons pas la limite. -1 signifie
// que nous ne savons pas encore combien d'entrées la table contient.
int num_total_entries_;
Variable globale (constante) :
Semblable aux membres de données, toutes les variables globales (constantes) doivent également être commentées avec leur signification et leur objectif, par exemple : //
Le nombre total de cas de tests que nous exécutons dans ce test de régression.
const int kNumTestCases = 6 ;
7.6.Commentaires sur la mise en œuvre
Commentez les endroits intelligents, obscurs, intéressants et importants dans le code d'implémentation.
Commentaires avant le code :
les commentaires doivent être ajoutés avant les blocs de code en suspens ou complexes, tels que :
// Diviser le résultat par deux, en tenant compte du fait que x
// contient le report de l'ajout.
for (int i = 0; i < result- >size(); i++) { x = (x << 8) + (*result)[i]; (*result)[i] = x >> 1; x &= 1; } Commentaire de ligne : plutôt obscur If vous souhaitez ajouter un commentaire à la fin de la ligne, vous pouvez ajouter un commentaire de fin de ligne avec deux espaces après le code, tel que : // Si nous avons suffisamment de mémoire, mappez également la partie données. mmap_budget = max (0, mmap_budget - index_->length() ); if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock)) return; // Erreur déjà enregistrée.
Notez qu'il existe deux blocs de commentaires décrivant ce code, et les commentaires mentionnent que des erreurs ont été enregistrées lors du retour de la fonction.
Il y a des commentaires sur les lignes adjacentes avant et après, qui peuvent être ajustés de manière appropriée pour les rendre plus lisibles :
…
DoSomething(); // Commentez ici pour que les commentaires s'alignent.
DoSomethingElseThatIsLonger(); // Commentez ici pour qu'il y ait deux espaces entre
// le code et le commentaire.
…
NULL, true/false, 1, 2, 3... :
lorsque vous transmettez une valeur booléenne ou un entier à une fonction, vous devez commenter la signification ou utiliser des constantes pour rendre le code compréhensible. Comparez :
bool success = CalculateSomething( interesting_value,
10,
false,
NULL); // Quels sont ces arguments ??
和:
bool success = CalculateSomething(interesting_value,
10, // Valeur de base par défaut.
false, // Ce n'est pas la première fois que nous appelons cela.
NULL); // Pas de rappel.
Utilisez des constantes ou des variables descriptives :
const int kDefaultBaseValue = 10 ;
const bool kFirstTimeCalling = false ;
Callback *null_callback = NULL ;
bool success = CalculateSomething(interesting_value,
kDefaultBaseValue,
kFirstTimeCalling,
null_callback);
À ne pas faire :
veillez à ne jamais utiliser de code de traduction en langage naturel comme commentaire, supposez que la personne qui lit votre code est meilleure que vous en C++ :D: //
Parcourez maintenant le tableau b et assurez-vous que si i se produit,
// l'élément suivant est i +1.
… // Bon sang, quel commentaire inutile.
7.7. Ponctuation, orthographe et grammaire
Faites attention à la ponctuation, à l’orthographe et à la grammaire : les commentaires bien rédigés sont beaucoup plus faciles à lire que les commentaires mal rédigés.
Les commentaires sont généralement des phrases complètes contenant des majuscules et des points (.) appropriés. Les commentaires plus courts (tels que les commentaires à la fin des lignes de code) peuvent être cliqués à votre guise, mais vous devez toujours faire attention à la cohérence du style. Les phrases complètes sont plus lisibles et indiquent que le commentaire est complet plutôt qu'une idée à moitié cuite.
Bien qu'il soit un peu gênant d'utiliser une virgule lorsque quelqu'un a souligné qu'il fallait utiliser un point-virgule. Un code clair et lisible est toujours important, et une ponctuation, une orthographe et une grammaire appropriées peuvent y contribuer.
7.8.Commentaires TODO
Utilisez les commentaires TODO pour des solutions temporaires à court terme ou pour du code suffisamment bon mais pas parfait.
De tels commentaires doivent utiliser la chaîne TODO en majuscules, suivie de votre nom, adresse e-mail, etc. entre parenthèses (parenthèses), et vous pouvez également ajouter deux points (deux-points) : le but est de rechercher selon le format TODO unifié : //
TODO ([email protected]) : utilisez ici un « * » pour l'opérateur de concaténation.
// TODO(Zeke) modifiez cela pour utiliser des relations.
Si vous l'ajoutez pour « faire quelque chose un jour dans le futur », vous pouvez ajouter une heure spécifique (« Correction d'ici novembre 2005 ») ou un événement (« Supprimez ce code lorsque tous les clients pourront gérer les réponses XML. »).
7.9.Résumé
- Concernant le style des commentaires, de nombreux codeurs C++ préfèrent les commentaires de ligne. Les codeurs C peuvent toujours avoir un faible pour les commentaires en bloc, ou utiliser des commentaires en bloc lorsqu'ils commentent de grandes sections d'en-têtes de fichiers ; 2. Les commentaires de fichiers peuvent montrer vos réalisations. C'est également pour
que d'autres peuvent vous rechercher si vous créez des ennuis ; - Les commentaires doivent être concis et précis, sans être tergiversants ou redondants. Simplifier des choses complexes et compliquer des choses simples sera méprisé ;
- Pour les codeurs chinois, c'est un problème de savoir s'il faut commenter en anglais ou en chinois. Mais quoi qu'il en soit, les commentaires doivent être compris par les autres. Est-ce pour montrer votre langue maternelle ou votre maîtrise d'une langue étrangère autre que les langages de programmation ?
- Les commentaires ne doivent pas être trop compliqués. Une indentation appropriée rendra les gens heureux de les lire. Cependant, il n'est pas nécessaire de spécifier de quelle colonne les commentaires doivent commencer (j'aime toujours cela lorsque j'écris du code). Sous UNIX/LINUX, vous pouvez également d'accord sur l'utilisation de la tabulation ou de l'espace, je préfère personnellement l'espace ;
- TODO est très bien. Parfois, les commentaires sont vraiment utilisés pour marquer des zones inachevées ou insatisfaisantes. De cette façon, après la recherche, vous saurez quel travail reste à faire et les journaux seront enregistrés.
8.Format
Le style et le format du code sont en effet relativement arbitraires, mais il est très facile pour tout le monde dans un projet de suivre le même style. En tant qu'individu, vous n'êtes peut-être pas d'accord avec chaque partie des règles de format suivantes, mais c'est important pour l'ensemble du projet. d'obéir à un style de programmation unifié, ce qui permet à chacun de lire et de comprendre plus facilement le code.
8.1.Longueur de la ligne
Le nombre de caractères dans chaque ligne de code ne doit pas dépasser 80.
Nous reconnaissons également que cette règle est controversée, mais avec autant de code qui la suit, nous pensons que la cohérence est plus importante.
Avantages : Les partisans de ce principe trouvent barbare de les obliger à redimensionner la fenêtre de leur éditeur. De nombreuses personnes ouvrent plusieurs fenêtres côte à côte en même temps, et il n'y a pas d'espace supplémentaire pour élargir une certaine fenêtre. Les gens limitent la taille maximale de la fenêtre et utilisent systématiquement une largeur de 80 colonnes. Pourquoi devrions-nous la changer ?
Inconvénients : les opposants à ce principe soutiennent que les lignes de code plus larges sont plus faciles à lire et que la limite de 80 colonnes est un défaut archaïque des ordinateurs centraux des années 1960 ; les appareils modernes ont des écrans plus larges et peuvent facilement afficher davantage de code.
Conclusion : 80 caractères est le maximum. exception:
- Si une ligne de commentaires contient une commande ou une URL qui dépasse 80 caractères, elle peut dépasser 80 caractères pour faciliter le copier-coller ;
- Ceux contenant de longs chemins peuvent dépasser 80 colonnes, alors essayez de les éviter ;
- La protection du fichier d'en-tête (empêchant l'inclusion répétée du premier article) peut ignorer ce principe.
8.2.Caractères non-ASCII
Essayez de ne pas utiliser de caractères non-ASCII. Lorsque vous les utilisez, vous devez utiliser le format UTF-8.
Le texte de l'interface utilisateur ne doit pas être codé en dur dans le code source, même s'il est en anglais, les caractères non-ASCII doivent donc être utilisés avec parcimonie. De tels caractères peuvent être inclus de manière appropriée dans des cas particuliers. Par exemple, lorsque le code analyse des fichiers de données externes, les chaînes non-ASCII utilisées comme délimiteurs dans les fichiers de données peuvent être codées en dur de manière appropriée ; plus communément, le code de test unitaire (qui ne nécessite pas de localisation ) peut contenir des chaînes non-ASCII. Dans de tels cas, UTF-8 doit être utilisé car son codage peut être compris et traité par de nombreux outils, tout comme le codage hexadécimal, surtout s'il améliore la lisibilité - comme dans "\xEF\xBB\ xBF" est un non de largeur nulle Unicode. -caractère d'espace de rupture invisible lorsqu'il est inclus dans un fichier source au format UTF-8.
8.3. Espaces et tabulations
Utilisez uniquement des espaces et indentez 2 espaces à la fois.
Utilisez des espaces pour l'indentation, n'utilisez pas de tabulations dans votre code et configurez votre éditeur pour convertir les tabulations en espaces.
8.4. Déclarations et définitions de fonctions
Le type de retour se trouve sur la même ligne que le nom de la fonction et, le cas échéant, les paramètres.
La fonction ressemble à ceci :
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) { DoSomething(); … } S'il y a trop de texte dans la même ligne pour accueillir tous les paramètres : ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2, Tapez par_name3) { DoSomething(); … } Même le premier paramètre ne peut pas tenir : ReturnType LongClassName::ReallyReallyReallyLongFunctionName( Tapez par_name1, // 4 espaces de retrait Tapez par_name2, Tapez par_name3) { DoSomething(); // 2 espaces de retrait … } Notez les points suivants :
- La valeur de retour est toujours sur la même ligne que le nom de la fonction ;
- La parenthèse gauche (parenthèse ouverte) est toujours sur la même ligne que le nom de la fonction ;
- Il n'y a pas d'espace entre le nom de la fonction et la parenthèse gauche ;
- Il n'y a pas d'espace entre les parenthèses et les paramètres ;
- L'accolade ouverte se trouve toujours à la fin de la même ligne que le dernier paramètre ;
- L'accolade fermante est toujours située seule sur la dernière ligne d'une fonction ;
- Il y a toujours un espace entre la parenthèse fermante et l'accolade ouvrante ;
- Tous les noms de paramètres formels lors de la déclaration et de l'implémentation de la fonction doivent être cohérents ;
- Tous les paramètres formels doivent être alignés autant que possible ;
- L'indentation par défaut est de 2 espaces ;
- Les paramètres emballés indépendamment restent indentés de 4 espaces.
Si la fonction est const, le mot-clé const doit être sur la même ligne que le dernier paramètre.
// Tout dans cette signature de fonction tient sur une seule ligne
ReturnType FunctionName(Type par) const { … }
// Cette signature de fonction nécessite plusieurs lignes, mais
// le mot-clé const est sur la ligne avec le dernier paramètre.
ReturnType ReallyLongFunctionName(Type par1,
Type par2) const { … } Si certains paramètres ne sont pas utilisés, modifiez les paramètres de la fonction définition Annoter le nom : // Toujours avoir des paramètres nommés dans les interfaces. class Shape { public: virtual void Rotate(double radians) = 0; }
// Toujours avoir des paramètres nommés dans la déclaration.
class Circle : public Shape { public: virtual void Rotate (double radians); }
// Commentez les paramètres nommés inutilisés dans les définitions.
void Circle::Rotate(double / radians /) {} // Mauvais - si quelqu'un veut l'implémenter plus tard, la signification de la variable //
n'est pas claire . void Circle :: Rotation (double) {}
8.5. Appels de fonction
Essayez de les mettre sur la même ligne, sinon mettez les paramètres réels entre parenthèses.
Les appels de fonction suivent la forme suivante :
bool retval = DoSomething(argument1, argument2, argument3);
Si la même ligne ne peut pas tenir, elle peut être divisée en plusieurs lignes. Chaque ligne suivante doit être alignée sur le premier paramètre réel. Ne laissez pas d'espace après la parenthèse gauche et avant la parenthèse droite : bool retval = DoSomething(averyveryverylongargument1
,
argument2 , argument3);
s'il existe de nombreux paramètres de fonction. Par souci de lisibilité, vous ne pouvez mettre qu'un seul paramètre dans chaque ligne :
bool retval = DoSomething(argument1,
argument2,
argument3,
argument4);
Si le nom de la fonction est trop long et dépasse la longueur de ligne maximale, vous pouvez mettre tous les paramètres sur des lignes séparées :
if (…) { … … if (…) { DoSomethingThatRequiresALongFunctionName( very_long_argument1, // 4 space indent argument2, argument3, argument4); }
8.6. Déclarations conditionnelles (Conditionnelles)
Il est également recommandé de ne pas ajouter d'espaces entre parenthèses, et de mettre le mot-clé else sur une nouvelle ligne.
Il existe deux formats acceptables pour les instructions conditionnelles de base, un avec des espaces entre les parenthèses et la condition, et un sans.
Le format le plus courant est le format sans espaces. N’importe quel format est acceptable, mais la cohérence est la clé. Si vous modifiez un fichier, référez-vous au format actuel ; si vous écrivez du nouveau code, référez-vous au format des autres fichiers du répertoire ou du projet. Si vous vous posez toujours la question, n'ajoutez pas d'espaces.
if (condition) { // pas d'espace entre parenthèses
… // retrait de 2 espaces.
} else { // Le else va sur la même ligne que l'accolade fermante.
…
}
Si vous préférez les espaces entre parenthèses :
if (condition) { // espaces entre parenthèses - rare
… // retrait de 2 espaces.
} else { // Le else va sur la même ligne que l'accolade fermante.
…
}
Notez que dans tous les cas, il y a un espace entre if et la parenthèse gauche, ainsi qu'entre la parenthèse droite et l'accolade gauche (si utilisée) :
if(condition) // Mauvais - espace manquant après IF.
if (condition) { // Mauvais - espace manquant avant {.
if(condition){ // Doublement mauvais.
if (condition) { // Bon - espace approprié après IF et avant {.
Certaines instructions conditionnelles sont écrites sur la même ligne pour améliorer la lisibilité, utilisez uniquement lorsque l'instruction est simple et n'utilise pas de clause else :
if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();
Si l'instruction a une branche else, elle est non autorisé :
/ / Non autorisé - Instruction IF sur une ligne lorsqu'il y a une clause ELSE
if (x) DoThis();
else DoThat();
Habituellement, les instructions sur une seule ligne n'ont pas besoin d'utiliser des accolades. Il est compréhensible si vous l'aimez. Certaines personnes exigent également que if soit utilisé. Accolades :
if (condition)
DoSomething(); // 2 espaces de retrait.
si (condition) { Faire quelque chose (); // Retrait de 2 espaces. }
Mais si une branche de l'instruction utilise des accolades, les autres parties doivent également les utiliser :
// Non autorisé - bouclé sur IF mais pas ELSE
if (condition) { foo; } else bar;
// Non autorisé - bouclé sur ELSE mais pas IF
if (condition)
foo;
sinon { barre ; }
// Des accolades autour de IF et ELSE sont requises car
// l'une des clauses utilisait des accolades.
si (condition) { foo; } autre { barre ; }
8.7. Boucles et instructions Switch
L'instruction switch peut être divisée en blocs à l'aide d'accolades ; le corps de la boucle vide doit utiliser {} ou continuer.
Le bloc case dans l'instruction switch peut ou non utiliser des accolades, selon vos préférences. Lorsque vous l'utilisez, suivez les instructions ci-dessous.
S'il existe une valeur qui ne répond pas aux conditions d'énumération du cas, incluez toujours une valeur par défaut (s'il existe une valeur d'entrée qui n'est pas traitée par le cas, le compilateur émettra une alarme). Si default ne sera jamais exécuté, vous pouvez simplement utiliser assert:
switch (var) { case 0: { // 2 space indent … // 4 space indent break; } case 1: { … break; } default: { assert(false ); } } Le corps de la boucle vide doit utiliser {} ou continuer au lieu d'un simple point-virgule : while (condition) { // Répétez le test jusqu'à ce qu'il renvoie false. } for (int i = 0; i < kSomeNumber; ++ i) {} // Bon - corps vide. while (condition) continue ; // Bon - continuer n'indique aucune logique.
tandis que (état); // Mauvais - ressemble à une partie de la boucle do/while.
8.8. Pointeurs et expressions de référence
Il ne doit y avoir aucun espace avant et après le point (.) ou la flèche (->), et il ne doit y avoir aucun espace après les opérateurs pointeur/adresse (*, &).
Voici des exemples corrects d'expressions de pointeur et de référence :
x = *p;
p = &x;
x = ry;
x = r->y;
Avis:
- Lors de l'accès aux membres, il n'y a pas d'espace avant ou après le point ou la flèche ;
- Il n'y a pas d'espace après l'opérateur de pointeur * ou &.
Lors de la déclaration de variables ou de paramètres de pointeur, l'astérisque peut apparaître immédiatement à côté du type ou du nom de la variable :
// C'est très bien, un espace précède.
char *c;
const string &str;
// C'est très bien, les espaces suivent.
char* c; // mais n'oubliez pas de faire "char* c, *d, *e, …;" !
const string& str;
char * c; // Mauvais - espaces des deux côtés of *
const string & str; // Mauvais - les espaces des deux côtés de &
doivent être au moins cohérents dans le même fichier (nouveau ou existant).
8.9.Expressions booléennes
Si une expression booléenne dépasse la largeur de ligne standard (80 caractères), les sauts de ligne doivent être unifiés.
Dans l'exemple suivant, l'opérateur logique AND (&&) est toujours situé à la fin de la ligne :
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another & last_one) { … } Les deux opérateurs logiques AND (&&) sont situés à la fin de la ligne, qui peut être envisagée. L'insertion de parenthèses supplémentaires peut être très utile pour améliorer la lisibilité si elle est utilisée correctement.
8.10. Valeurs de retour de fonction (Valeurs de retour)
N'utilisez pas de parenthèses dans les expressions de retour.
N'utilisez pas de parenthèses lorsqu'une fonction renvoie :
return x; // not return(x);
8.11. Initialisation des variables et des tableaux
Choisissez = ou ().
Vous devez choisir entre les deux. Les formes suivantes sont correctes :
int x = 3;
int x(3);
string name("Some Name");
string name = "Some Name";
8.12. Directives du préprocesseur
N'indentez pas les directives de prétraitement, commencez au début de la ligne.
Même si les directives de prétraitement se trouvent dans un bloc de code en retrait, elles doivent commencer au début de la ligne.
// Bon - directives en début de ligne
if (lopside_score) { #if DISASTER_PENDING // Correct – Commence au début de la ligne DropEverything(); #endif BackToNormal(); } // Mauvais - directives en retrait if (lopside_score) { #if DISASTER_PENDING // Faux ! Le "#if" doit être au début de la ligne DropEverything(); #endif // Faux ! Ne pas mettre en retrait "#endif" BackToNormal(); }
8.13. Format des cours
Les attributs déclarés sont public :, protected :, private : dans l'ordre, indentés d'1 espace à chaque fois (Note du traducteur, pourquoi pas deux ? Certains prônent le private d'abord, afin qu'il soit clair d'un coup d'œil quelles données membres sont déclarées, et certaines personnes préconisent qu'il est logique de regrouper les variables et les opérations selon des relations logiques :-)).
Le format de base d'une déclaration de classe (si vous ne connaissez pas les annotations de classe, reportez-vous à la section Annotations de classe dans la partie 6) est le suivant :
class MyClass : public OtherClass { public: // Notez le retrait de 1 espace ! MyClass( ); // Retrait d'espace régulier de 2. explicite MyClass(int var); ~MyClass() {}
void UneFonction();
void SomeFunctionThatDoesNothing() { }
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
privé :
bool SomeInternalFunction();
int une_var_;
int some_other_var_;
DISALLOW_COPY_AND_ASSIGN(MaClasse);
};
注意:
- Par conséquent, le nom de la classe de base doit être placé sur la même ligne que le nom de la sous-classe, autant que possible dans la limite de 80 colonnes ;
- Les mots-clés public :, protected :, private : doivent être indentés d'1 espace (Note du traducteur, MSVC utilise principalement l'indentation par tabulation, et ces trois mots-clés ne sont pas en retrait) ;
- À l'exception du premier mot-clé (généralement public), laissez une ligne vide avant les autres mots-clés. Si la classe est relativement petite, elle n'a pas besoin d'être vide ;
- Ne laissez pas de ligne vide après ces mots-clés ;
- Public est placé en premier, suivi de protégé et privé ;
- Pour l'ordre de déclaration, veuillez vous référer à la section « Ordonnance de déclaration » dans la partie 3.
8.14.Listes d'initialisation
Mettez la liste d'initialisation du constructeur sur la même ligne ou sur plusieurs lignes indentées de quatre espaces.
Deux formats de liste d'initialisation acceptables :
// Lorsque tout tient sur une seule ligne :
MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) { ou // Lorsqu'il nécessite plusieurs lignes, indentez 4 espaces, mettre les deux points sur // la première ligne d'initialisation : MyClass::MyClass(int var) : some_var_(var), // 4 espaces en retrait some_other_var_(var + 1) { // alignés … DoSomething(); … }
8.15. Formatage de l'espace de noms
Le contenu de l'espace de noms n'est pas en retrait.
Les espaces de noms n'ajoutent pas de niveaux d'indentation supplémentaires, par exemple :
espace de noms {
void foo() { // Correct. Aucune indentation supplémentaire dans l'espace de noms.
…
}
} // espace de noms
Ne pas mettre en retrait :
espace de noms {
// Faux. En retrait alors qu'il ne devrait pas l'être.
vide foo() { … }
} // espace de noms
8.16. Espaces horizontaux
Description du produit
:
void f(bool b) { // Les accolades ouvertes doivent toujours être précédées d'un espace.
…
int je = 0; // Les points-virgules n'ont généralement pas d'espace devant eux.
int x[] = {0}; // Les espaces entre accolades pour l'initialisation du tableau sont
int x[] = {0}; // facultatif. Si vous les utilisez, mettez-les des deux côtés !
// Espaces autour des deux points dans les listes d'héritage et d'initialisation.
class Foo : public Bar { public: // Pour les implémentations de fonctions en ligne, placez des espaces entre les accolades // et l'implémentation elle-même. Foo(int b) : Bar(), baz_(b) {} // Aucun espace entre accolades vides. void Réinitialiser() { baz_ = 0; } // Espaces séparant les accolades de l'implémentation. …
L'ajout d'un espace blanc redondant créera une charge supplémentaire de modification pour les autres, alors n'incluez pas d'espace blanc supplémentaire. Si vous êtes sûr qu'une ligne de code a été modifiée, supprimez les espaces supplémentaires ; ou supprimez-les lors du nettoyage spécifique des espaces (assurez-vous que personne d'autre ne l'utilise).
Boucles et instructions conditionnelles :
if (b) { // Espace après le mot-clé dans les conditions et les boucles.
} else { // Espaces autour de else.
}
while (test) {} // Il n'y a généralement pas d'espace entre parenthèses.
switch (i ) { for (int i = 0; i < 5; ++i) { switch ( i ) { // Les boucles et les conditions peuvent contenir des espaces if ( test ) { // des parenthèses, mais c'est rare. Soyez cohérent. for ( int i = 0; i < 5; ++i ) { for ( ; i < 5 ; ++i) { // Les boucles For ont toujours un espace après le … // point-virgule et peuvent avoir un espace avant le / / point-virgule.switch (i) {
cas 1 : // Pas d'espace avant deux points dans un cas de commutation.
…
cas 2 : rupture ; // Utilisez un espace après les deux-points s'il y a du code après.
操作符:
x = 0; // Les opérateurs d'affectation sont toujours entourés d'espaces
//.
x = -5 ; // Aucun espace ne sépare les opérateurs unaires et leurs
++x ; // arguments.
si (x && !y)
…
v = w * x + y / z; // Les opérateurs binaires sont généralement entourés d'espaces,
v = w x + y/z; // mais vous pouvez supprimer les espaces autour des facteurs.
v = w * (x + z); // Les parenthèses ne doivent contenir aucun espace.
模板和转换:
vector x; // Aucun espace à l'intérieur de l'angle
y = static_cast<char >(x); // crochets (< et >), avant
// <, ou entre >( dans un cast.
vector<char *> x; // Les espaces entre le type et le pointeur sont
// corrects, mais soyez cohérents.
set<list > x; // C++ nécessite un espace dans > > .
set< list > x; // Vous pouvez éventuellement utiliser
// un espacement symétrique dans < <.
8.17. Espaces verticaux
Moins il y a d’espace blanc vertical, mieux c’est.
Ce n'est pas seulement une règle mais une question de principe : n'utilisez pas de lignes vides sauf en cas d'absolue nécessité. En particulier : ne laissez pas plus de 2 lignes vides entre deux définitions de fonction, n'ayez pas de lignes vides au début et à la fin du corps de la fonction, et n'ajoutez pas de lignes vides à volonté dans le corps de la fonction.
Le principe de base est le suivant : plus il y a de code pouvant être affiché sur le même écran, plus il est facile de comprendre le flux de contrôle du programme. Bien sûr, un bloc de code trop dense est tout aussi inesthétique qu’un bloc trop clairsemé, c’est donc à votre discrétion, mais en général, moins c’est mieux.
Il ne doit y avoir aucune ligne vide au début et à la fin de la fonction :
void Function() {
// Lignes vides inutiles avant et après
}
Il ne doit y avoir aucune ligne vide au début et à la fin du bloc de code :
while (condition) { // Ligne vide inutile après
}
si (condition) {
// Ligne vide inutile avant
}
Une ligne vide entre les blocs if-else est également acceptable :
if (condition) { // Certaines lignes de code trop petites pour passer à une autre fonction, // suivies d'une ligne vide.
} else { // Un autre bloc de code }
8.18.Résumé
- En principe, la largeur des lignes ne doit pas dépasser 80 colonnes, ce qui occuperait la totalité de l'écran d'affichage de 22 pouces, ce qui est injustifiable ;
- Essayez de ne pas utiliser de caractères non-ASCII. Si vous les utilisez, référez-vous au format UTF-8 (surtout sous UNIX/Linux, les caractères larges peuvent être pris en compte sous Windows), et essayez de ne pas coupler de constantes chaîne dans le code, comme créer des fichiers de ressources indépendants. Ce n’est plus seulement une question de style ;
- Utilisez les espaces sans condition sous UNIX/Linux, et il n'y a rien de mal à utiliser Tab sous MSVC ;
- Paramètres de fonction, conditions logiques et listes d'initialisation : soit tous les paramètres et noms de fonctions sont placés sur la même ligne, soit tous les paramètres sont séparés sur des lignes distinctes ;
- En plus de l'accolade ouvrante des définitions de fonction qui peut être placée au début de la ligne, les accolades ouvrantes comprenant les déclarations de fonction/classe/structure/énumération et diverses instructions peuvent être placées à la fin de la ligne, et toutes les accolades fermantes sont sur des lignes distinctes ;
- Ne laissez pas d'espaces avant et après l'opérateur ./->. Ne laissez pas d'espaces avant et après */&. Un seul opérateur suffit. Vous pouvez choisir gauche ou droite selon vos préférences ;
- Les directives/espaces de noms de prétraitement n'utilisent pas d'indentation supplémentaire, les classes/structures/énumérations/fonctions/instructions utilisent l'indentation ;
- L'utilisation de = ou () pour l'initialisation dépend des préférences personnelles, restez simplement cohérent ;
- N'ajoutez pas () pour revenir ;
- N’abusez pas des espaces blancs horizontaux/verticaux. Facilitez la lecture.
9.Exceptions aux règles
Les pratiques de codage expliquées précédemment sont fondamentalement obligatoires, mais toutes les bonnes règles autorisent des exceptions.
9.1. Code non conforme existant
Le code existant non conforme au style de programmation établi peut être toléré.
Lorsque vous modifiez du code qui utilise d'autres styles, vous n'avez pas besoin d'utiliser les conventions de ce guide pour le maintenir cohérent avec le style d'origine du code. Si vous êtes inquiet, vous pouvez en discuter avec l'auteur original du code ou avec le responsable actuel. N'oubliez pas que la cohérence inclut la cohérence originale.
9.2.Code Windows
Les programmeurs Windows ont leurs propres habitudes de codage, principalement dérivées de certains fichiers d'en-tête Windows et d'autres codes Microsoft. Nous voulons que tout le monde puisse lire votre code, c'est pourquoi nous proposons un guide distinct sur le codage C++ pour toutes les plateformes.
Si vous avez utilisé le style de codage Windows, il est nécessaire de réitérer certaines directives que vous avez peut-être oubliées (Note du traducteur, pourquoi j'ai l'impression de subir un lavage de cerveau :D) :
- N'utilisez pas la notation hongroise (par exemple en définissant une variable entière comme iNum), utilisez les conventions de dénomination de Google, notamment en utilisant l'extension .cc pour les fichiers sources ;
- Windows a défini de nombreux synonymes pour les types intégrés d'origine (note du traducteur, cela me dégoûte également), tels que DWORD, HANDLE, etc. Ceci est tout à fait acceptable et même encouragé lors de l'appel de l'API Windows, mais vous devriez quand même essayer de utilisez-le. Les types C++ originaux, par exemple, utilisent const TCHAR* au lieu de LPCTSTR ;
- Lors de la compilation avec Microsoft Visual C++, définissez le niveau d'avertissement sur 3 ou plus et traitez tous les avertissements comme des erreurs ;
- N'utilisez pas #pragma une seule fois ; comme protection d'inclusion, utilisez la protection d'inclusion standard C++ et incluez le chemin du fichier protégé au niveau supérieur de l'arborescence du projet (Note du traducteur, #include<prj_name/public/tools.h>) ;
- Sauf nécessité absolue, n'utilisez pas d'extensions non standard telles que #pragma et __declspec. L'utilisation de __declspec(dllimport) et __declspec(dllexport) est autorisée, mais les macros telles que DLLIMPORT et DLLEXPORT doivent être transmises pour que d'autres puissent les utiliser. codes lors du partage Il est facile d'abandonner ces extensions.
Sous Windows, seules quelques règles peuvent occasionnellement être enfreintes : - Habituellement, il nous est interdit d'utiliser l'héritage multiple, mais vous pouvez utiliser l'héritage multiple lorsque vous utilisez les classes COM et ATL/WTL. Afin d'exécuter les classes COM ou ATL/WTL et leurs interfaces, vous pouvez utiliser l'héritage d'implémentation multiple ;
- Bien que les exceptions ne doivent pas être utilisées dans le code, les exceptions sont largement utilisées dans ATL et dans certains STL (y compris le STL de Visual C++). Lors de l'utilisation d'ATL, _ATL_NO_EXCEPTIONS doit être défini pour bloquer les exceptions. Vous devez étudier si les exceptions STL sont également bloquées. , s'il n'est pas protégé, vous pouvez également activer les exceptions du compilateur. Notez que cela concerne uniquement la compilation de STL. Vous ne devez toujours pas écrire de code contenant la gestion des exceptions ;
- Habituellement, chaque fichier source de chaque projet contient un fichier d'en-tête nommé StdAfx.h ou precompile.h pour faciliter la pré-compilation du fichier d'en-tête. Afin de faciliter le partage du code avec d'autres projets, évitez d'inclure explicitement ce fichier (precompile.cc Sauf ), utilisez l'option du compilateur /FI pour inclure automatiquement ;
- Les fichiers d'en-tête de ressource, généralement nommés resource.h et contenant uniquement des macros, n'ont pas besoin d'adhérer à ce guide de style.
9.3.Travail d'équipe
Faites preuve de bon sens et soyez cohérent.
Lors de l'édition du code, prenez un moment pour examiner l'autre code du projet et déterminer son style. Si un autre code utilise des espaces dans les instructions if, utilisez-les également. Si les commentaires sont encadrés par des
astérisques ( ), vous faites de même : / ******************************* ****
- Quelques commentaires sont ici.
- Il peut y avoir plusieurs lignes.
***********************************/
L'intérêt d'utiliser un style de programmation Le guide est de fournir un Avec des normes de codage communes, tout le monde peut se concentrer sur la mise en œuvre du contenu plutôt que sur la présentation. Nous avons donné des spécifications de style globales, mais le style local est également très important. Si le nouveau code que vous ajoutez dans un fichier est loin du style de code d'origine, cela détruira la beauté globale du fichier lui-même et affectera la lecture. Essayez donc d'éviter il.
D'accord, j'ai presque écrit sur le style de codage. Le code lui-même est plus intéressant, alors profitez-en !