web-dev-qa-db-fra.com

Où déclarer / définir des constantes d'étendue de classe en C ++?

Je suis curieux de connaître les avantages/inconvénients des différentes options de déclaration constante et de définition en C++. Depuis très longtemps, je viens de les déclarer en haut du fichier d'en-tête avant la définition de la classe:

//.h
const int MyConst = 10;
const string MyStrConst = "String";
class MyClass {
...
};

Bien que cela pollue l'espace de noms global (ce que je sais est une mauvaise chose, mais je n'ai jamais trouvé de liste de raisons pour lesquelles il est mauvais), les constantes seront toujours étendues à des unités de traduction individuelles, donc les fichiers qui n'incluent pas cet en-tête n'aura pas accès à ces constantes. Mais vous pouvez obtenir des collisions de noms si d'autres classes définissent une constante du même nom, ce qui n'est sans doute pas une mauvaise chose car cela peut être une bonne indication d'une zone qui pourrait être refactorisée.

Récemment, j'ai décidé qu'il serait préférable de déclarer des constantes spécifiques à la classe à l'intérieur de la définition de classe elle-même:

//.h
class MyClass {
    public:
         static const int MyConst = 10;
...
    private:
         static const string MyStrConst;
...
};
//.cpp
const string MyClass::MyStrConst = "String";

La visibilité de la constante serait ajustée selon que la constante est utilisée uniquement en interne à la classe ou est nécessaire pour d'autres objets qui utilisent la classe. C'est ce que je pense être la meilleure option en ce moment, principalement parce que vous pouvez garder les constantes de classe internes privées pour la classe et toutes les autres classes utilisant les constantes publiques auraient une référence plus détaillée à la source de la constante (par exemple MyClass: : MyConst). Il ne polluera pas non plus l'espace de noms global. Bien qu'il présente le désavantage d'exiger une initialisation non intégrale dans le fichier cpp.

J'ai également envisagé de déplacer les constantes dans leur propre fichier d'en-tête et de les encapsuler dans un espace de noms au cas où une autre classe aurait besoin des constantes, mais pas de la définition de classe entière.

Je cherchais simplement des opinions et peut-être d'autres options que je n'avais pas encore envisagées.

59
bsruth

Votre affirmation selon laquelle déclarer une constante non intégrale en tant que membre de classe statique "au détriment d'exiger une initialisation non intégrale dans le fichier cpp" n'est pas exactement solide, pour ainsi dire. Cela nécessite une définition dans le fichier cpp, mais ce n'est pas un "détriment", c'est une question d'intention. L'objet const au niveau de l'espace de noms en C++ a une liaison interne par défaut, ce qui signifie que dans votre variante d'origine, la déclaration

const string MyStrConst = "String"; 

est équivalent à

static const string MyStrConst = "String"; 

c'est-à-dire qu'il définira un objet MyStrConst indépendant dans chaque unité de traduction dans laquelle ce fichier d'en-tête est inclus. Etes-vous conscient de cela? Était-ce votre intention ou non?

Dans tous les cas, si vous n'avez pas spécifiquement besoin d'un objet séparé dans chaque unité de traduction, la déclaration de la constante MyStrConst dans votre exemple d'origine n'est pas une bonne pratique. Normalement, vous ne mettriez qu'une déclaration non définissante dans le fichier d'en-tête

extern const string MyStrConst; 

et fournir une définition dans le fichier cpp

const string MyStrConst = "String";

s'assurant ainsi que le programme entier utilise le même objet constant. En d'autres termes, lorsqu'il s'agit de constantes non intégrales, une pratique normale consiste à les définir dans un fichier cpp. Donc, quelle que soit la façon dont vous le déclarez (dans la classe ou en dehors), vous devrez normalement toujours faire face au "détriment" d'avoir à le définir dans le fichier cpp. Bien sûr, comme je l'ai dit ci-dessus, avec les constantes d'espace de noms, vous pouvez vous en tirer avec ce que vous avez dans votre première variante, mais ce ne serait qu'un exemple de "codage paresseux".

Quoi qu'il en soit, je ne pense pas qu'il y ait une raison de trop compliquer le problème: si la constante a un "attachement" évident à la classe, elle devrait être déclarée comme membre de la classe.

P.S. Les spécificateurs d'accès (public, protected, private) ne contrôlent pas visibilité du nom. Ils contrôlent uniquement son accessibilité. Le nom reste visible dans tous les cas.

45
AnT

La pollution de l'espace de noms global est mauvaise car quelqu'un (par exemple, le rédacteur d'une bibliothèque que vous utilisez) peut vouloir utiliser le nom MyConst dans un autre but. Cela peut entraîner de graves problèmes (bibliothèques qui ne peuvent pas être utilisées ensemble, etc.)

Votre deuxième solution est clairement la meilleure si les constantes sont liées à une seule classe. Si ce n'est pas si facile (pensez à des constantes physiques ou mathématiques sans liens avec une classe de votre programme), la solution d'espace de noms est meilleure que cela. BTW: si vous devez être compatible avec les anciens compilateurs C++, souvenez-vous que certains d'entre eux ne peuvent pas utiliser l'initialisation intégrale dans un fichier d'en-tête - vous devez initialiser dans le fichier C++ ou utiliser l'ancienne astuce enum dans ce cas.

Je pense qu'il n'y a pas de meilleure option pour les constantes - du moins, je ne peux pas en penser une pour le moment ...

10
hjhill

La pollution de l'espace de noms mondial devrait être évidemment mauvaise. Si j'inclus un fichier d'en-tête, je ne veux pas rencontrer ou déboguer des collisions de noms avec des constantes déclarées dans cet en-tête. Ces types d'erreurs sont vraiment frustrants et parfois difficiles à diagnostiquer. Par exemple, j'ai dû une fois établir un lien avec un projet qui avait ceci défini dans un en-tête:

#define read _read

Si vos constantes sont une pollution d'espace de noms, il s'agit de déchets nucléaires d'espace de noms. La manifestation de ceci était une série d'erreurs de compilation très étranges se plaignant de manquer la fonction _read, mais uniquement lors de la liaison avec cette bibliothèque. Nous avons finalement renommé les fonctions de lecture en quelque chose d'autre, ce qui n'est pas difficile mais devrait être inutile.

Votre deuxième solution est très raisonnable car elle met la variable dans le champ d'application. Il n'y a aucune raison que cela doive être associé à une classe, et si j'ai besoin de partager des constantes entre les classes, je déclarerai des constantes dans leur propre espace de noms et fichier d'en-tête. Ce n'est pas idéal pour la compilation, mais parfois c'est nécessaire.

J'ai également vu des gens mettre des constantes dans leur propre classe, qui peut être implémentée en tant que singleton. Cela me semble fonctionner sans récompense, le langage vous offre quelques facilités pour déclarer des constantes.

5
James Thompson

Si une seule classe va utiliser ces constantes, déclarez-les comme static const à l'intérieur du corps de la classe. Si un groupe de classes liées va utiliser les constantes, déclarez-les soit dans une classe/structure qui ne contient que les constantes et les méthodes utilitaires, soit dans un espace de noms dédié. Par exemple,

namespace MyAppAudioConstants
{
     //declare constants here
}

S'il s'agit de constantes utilisées par l'application entière (ou de gros morceaux de celle-ci), déclarez-les dans un espace de noms dans un en-tête qui est (implicitement ou explicitement) inclus partout.

namespace MyAppGlobalConstants
{
    //declare constants here
}
2
Chinmay Kanchi

Vous pouvez les déclarer comme globaux dans le fichier c ++, tant qu'ils ne sont pas référencés dans l'en-tête. Ensuite, ils sont privés de cette classe et ne pollueront pas l'espace de noms global.

2
Alex Brown

Personnellement, j'utilise votre deuxième approche; Je l'utilise depuis des années et ça marche bien pour moi.

D'un point de visibilité, j'aurais tendance à faire des statiques de niveau fichier de constantes privées car personne en dehors du fichier d'implémentation n'a besoin de savoir qu'elles existent; cela permet d'éviter la recompilation de la réaction en chaîne si vous devez modifier leurs noms ou en ajouter de nouveaux car leur portée de nom est identique à leur portée d'utilisation ...

2
Len Holgate