web-dev-qa-db-fra.com

Définir des variables constantes dans l'en-tête C++

Un programme sur lequel je travaille a de nombreuses constantes qui s'appliquent à toutes les classes. Je veux faire un fichier d'en-tête "Constants.h", et pouvoir déclarer toutes les constantes pertinentes. Ensuite, dans mes autres classes, je peux simplement inclure #include "Constants.h

Je l’ai bien fait en utilisant la syntaxe #ifndef ... #define .... Cependant, je préférerais utiliser la forme de constantes const int.... Je ne sais pas trop comment faire. 

40
user1599559

Vous pouvez simplement définir une série de const ints dans un fichier d’en-tête:

// Constants.h
#if !defined(MYLIB_CONSTANTS_H)
#define MYLIB_CONSTANTS_H 1

const int a = 100;
const int b = 0x7f;

#endif

Cela fonctionne car en C++, un nom situé dans la portée de l'espace de noms (y compris l'espace de noms global) explicitement déclaré const et non explicitement déclaré externe possède une liaison interne. Par conséquent, ces variables ne provoquent pas de duplication de symboles lorsque vous liez des unités de traduction. Sinon, vous pouvez explicitement déclarer les constantes comme statiques.

static const int a = 100;
static const int b = 0x7f;

Ceci est plus compatible avec C et plus lisible par des personnes qui ne sont peut-être pas familiarisées avec les règles de liaison C++.

Si toutes les constantes sont des entiers, une autre méthode que vous pouvez utiliser consiste à déclarer les identificateurs en tant qu'énums.

enum mylib_constants {
    a = 100;
    b = 0x7f;
};

Toutes ces méthodes utilisent uniquement un en-tête et permettent aux noms déclarés d'être utilisés en tant que constantes de compilation. L'utilisation de extern const int et d'un fichier d'implémentation séparé empêche les noms d'être utilisés en tant que constantes de temps de compilation.


Notez que la règle qui rend certaines constantes implicitement un lien interne fait s'applique aux pointeurs, exactement comme les constantes d’autres types. La difficulté réside toutefois dans le fait que marquer un pointeur comme étant const requiert une syntaxe légèrement différente de celle utilisée par la plupart des gens pour créer des variables d’autres types const. Tu as besoin de faire:

int * const ptr;

faire un pointeur constant, de sorte que la règle lui soit appliquée.

Notez également que c’est une des raisons pour lesquelles je préfère systématiquement mettre const après le type: int const au lieu de const int. J'ai également mis le * à côté de la variable: i.e. int *ptr; au lieu de int* ptr;.

J'aime faire ce genre de choses parce qu'elles reflètent le cas général du fonctionnement réel du C++. Les alternatives (const int, int* p) ne sont que des cas spéciaux pour rendre certaines choses simples plus lisibles. Le problème est que lorsque vous sortez de ces cas simples, les alternatives spéciales en cas de cas deviennent activement trompeuses.

Ainsi, bien que les exemples précédents montrent l'utilisation courante de const, je recommanderais en fait aux personnes de les écrire comme ceci:

int const a = 100;
int const b = 0x7f;

et 

static int const a = 100;
static int const b = 0x7f;
56
bames53

J'aime le namespace better pour ce genre de but.

Option 1 :

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H

//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace LibConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}
#endif

// source.cpp
#include <LibConstants.hpp>
int value = LibConstants::CurlTimeOut;

Option 2 :

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H
//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace CurlConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}

namespace MySQLConstants
{
  const int DBPoolSize = 0xFF;      // Just some example
  ...
}
#endif



// source.cpp
#include <LibConstants.hpp>
int value = CurlConstants::CurlTimeOut;
int val2  = MySQLConstants::DBPoolSize;

Et je n’utiliserais jamais une classe pour contenir ce type de variables HardCoded Const. 

24
Manikanda raj S

Vous ne devriez généralement pas utiliser, par exemple, const int dans un fichier d'en-tête, s'il est inclus dans plusieurs fichiers sources. En effet, les variables seront définies une fois par fichier source (unités de traduction du point de vue technique), car les variables global const sont implicitement statiques , occupant plus de mémoire que nécessaire.

Vous devriez plutôt avoir un fichier source spécial, Constants.cpp, qui définit réellement les variables, puis les déclarer comme étant extern dans le fichier d'en-tête.

Quelque chose comme ce fichier d'en-tête:

// Protect against multiple inclusions in the same source file
#ifndef CONSTANTS_H
#define CONSTANTS_H

extern const int CONSTANT_1;

#endif

Et ceci dans un fichier source:

const int CONSTANT_1 = 123;
7

Plutôt que de créer un ensemble de variables globales, vous pouvez envisager de créer une classe comportant un ensemble de constantes statiques publiques. Il reste toujours global, mais de cette façon, il est encapsulé dans une classe afin que vous sachiez d'où vient la constante et qu'elle est supposée être une constante.

Constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

class GlobalConstants {
  public:
    static const int myConstant;
    static const int myOtherConstant;
};

#endif

Constants.cpp

#include "Constants.h"

const int GlobalConstants::myConstant = 1;
const int GlobalConstants::myOtherConstant = 3;

Ensuite, vous pouvez utiliser ceci comme ceci:

#include "Constants.h"

void foo() {
  int foo = GlobalConstants::myConstant;
}
0
Andrew Rasmussen

Il semble que la réponse de bames53 puisse être étendue à la définition de valeurs constantes entières et non entières dans un espace de noms et une classe declarations même si elles sont incluses dans plusieurs fichiers source. Il n'est pas nécessaire de placer les déclarations dans un fichier d'en-tête, mais les définitions dans un fichier source. L'exemple suivant fonctionne pour Microsoft Visual Studio 2015, pour z/OS V2.2 XL C/C++ sur OS/390 et pour g ++ (GCC) 8.1.1 20180502 sur GNU/Linux 4.16.14 (Fedora 28). Notez que les constantes sont déclarées/définies dans un seul fichier d’en-tête qui est inclus dans plusieurs fichiers source.

Dans foo.cc:

#include <cstdio>               // for puts

#include "messages.hh"
#include "bar.hh"
#include "Zoo.hh"

int main(int argc, const char* argv[])
{
  puts("Hello!");
  bar();
  Zoo();
  puts(Message::third);
  return 0;
}

Dans messages.hh:

#ifndef MESSAGES_HH
#define MESSAGES_HH

namespace Message {
  char const * const first = "Yes, this is the first message!";
  char const * const second = "This is the second message.";
  char const * const third = "Message #3.";
};

#endif

En bar.cc:

#include "messages.hh"
#include <cstdio>

void bar(void)
{
  puts("Wow!");
  printf("bar: %s\n", Message::first);
}

Dans Zoo.cc:

#include <cstdio>
#include "messages.hh"

void Zoo(void)
{
  printf("Zoo: %s\n", Message::second);
}

En bar.hh:

#ifndef BAR_HH
#define BAR_HH

#include "messages.hh"

void bar(void);

#endif

Dans Zoo.hh:

#ifndef Zoo_HH
#define Zoo_HH

#include "messages.hh"

void Zoo(void);

#endif

Cela donne la sortie suivante:

Hello!
Wow!
bar: Yes, this is the first message!
Zoo: This is the second message.
Message #3.

Le type de données char const * const signifie un pointeur constant sur un tableau de caractères constants. La première const est nécessaire car (selon g ++) "ISO C++ interdit la conversion d'une constante de chaîne en" char * ". La seconde const est nécessaire pour éviter les erreurs de liaison dues à plusieurs définitions des constantes (alors insuffisamment constantes). Votre compilateur peut ne pas se plaindre si vous omettez un ou les deux consts, mais le code source est alors moins portable.

0
Louis Strous

C++ 17 inline variables

Cette fonctionnalité géniale de C++ 17 nous permet de:

  • utilisez simplement une seule adresse mémoire pour chaque constante
  • stockez-le en tant que constexpr: Comment déclarer constexpr extern?
  • faites-le en une seule ligne à partir d'un en-tête

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Compiler et exécuter:

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

GitHub en amont .

Voir aussi: Comment fonctionnent les variables en ligne?

Norme C++ sur les variables en ligne

La norme C++ garantit que les adresses seront les mêmes. Projet de norme C++ 17 N4659 10.1.6 "Le spécificateur en ligne":

6 Une fonction ou une variable en ligne avec une liaison externe doit avoir la même adresse dans toutes les unités de traduction.

cppreference https://fr.cppreference.com/w/cpp/language/inline explique que si static n'est pas fourni, il est doté d'un lien externe.

Implémentation de variable en ligne

Nous pouvons observer comment cela est implémenté avec:

nm main.o notmain.o

qui contient:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

et man nm dit à propos de u:

"u" Le symbole est un symbole global unique. Il s’agit d’une extension GNU de l’ensemble standard de liaisons de symboles ELF. Pour un tel symbole, l’éditeur de liens dynamique s’assurera que dans tout le processus il n'y a qu'un seul symbole portant ce nom et ce type utilisé.

nous voyons donc qu’il existe une extension ELF dédiée à cela.

C++ 17 brouillon standard sur const implique static

Ceci est la citation pour ce qui a été mentionné à: https://stackoverflow.com/a/12043198/895245

Projet de norme C++ 17 n4659 6.5 "Programme et liaison":

3 Un nom ayant une portée d’espace de nommage (6.3.6) a une liaison interne s’il s’agit du nom de

  • (3.1) - une variable, une fonction ou un modèle de fonction explicitement déclaré statique; ou,
  • (3.2) - une variable non inline de type qual-constant non volatile qui n'est ni explicitement déclarée extern ni précédemment déclaré avoir un lien externe; ou
  • (3.3) - un membre de données d'une union anonyme.

Annexe C (informative) Compatibilité, C.1.2 L'Article 6: "concepts de base" explique pourquoi il a été remplacé par C:

6.5 [également 10.1.7]

Changement: Un nom de fichier qui est explicitement déclaré const, et non explicitement déclaré extern, a lien interne, alors qu'en C, il aurait un lien externe.

Justification: étant donné que les objets const peuvent être utilisés comme valeurs lors de la traduction en C++, cette fonctionnalité recommande vivement Les programmeurs doivent fournir un initialiseur explicite pour chaque objet const. Cette fonctionnalité permet à l'utilisateur de mettre Objets const dans les fichiers source inclus dans plusieurs unités de traduction.

Effet sur la fonctionnalité d'origine: Changez en sémantique pour une fonctionnalité bien définie.

Difficulté de conversion: transformation sémantique.

Comment largement utilisé: Rarement.

Voir aussi: Pourquoi const implique-t-il une liaison interne en C++, alors que ce n'est pas le cas en C?