105 votes

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

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

Je l'ai fait fonctionner correctement en utilisant #ifndef ... #define ... syntaxe. Toutefois, je préférerais utiliser la const int... sous forme de constantes. Mais je ne sais pas vraiment comment faire.

131voto

bames53 Points 38303

Vous pourriez 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 parce qu'en C++, un nom dans l'espace de noms (y compris l'espace de noms global) qui est explicitement déclaré const et qui n'est pas explicitement déclaré extern a un lien interne, de sorte que ces variables ne causeront pas de symboles en double lorsque vous liez ensemble des unités de traduction. Vous pourriez également déclarer explicitement les constantes comme étant statiques.

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

Cela est plus compatible avec le C et plus lisible pour les personnes qui ne sont pas familières avec les règles de liaison du C++.

Si toutes les constantes sont des ints, une autre méthode consiste à déclarer les identifiants comme des enums.

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

Toutes ces méthodes n'utilisent qu'un en-tête et permettent aux noms déclarés d'être utilisés comme constantes de compilation. Utilisation de extern const int et un fichier d'implémentation séparé empêche les noms d'être utilisés comme constantes de compilation.


Notez que la règle qui fait de certaines constantes des liens internes implicites fait s'appliquent aux pointeurs, exactement comme les constantes des autres types. Le problème est que le fait de marquer un pointeur en tant que const requiert une syntaxe un peu différente de celle que la plupart des gens utilisent pour rendre const des variables d'autres types. Vous devez le faire :

int * const ptr;

pour en faire un pointeur constant, afin que la règle s'y applique.

Notez aussi que c'est l'une des raisons pour lesquelles je préfère mettre systématiquement const après le type : int const au lieu de const int . J'ai aussi mis le * à côté de la variable : c'est-à-dire int *ptr; au lieu de int* ptr; (comparer également este discussion).

J'aime faire ce genre de choses car elles reflètent le cas général du fonctionnement réel du C++. Les alternatives ( const int , int* p ) sont juste des cas particuliers pour rendre certaines choses simples plus lisibles. Le problème est que lorsque l'on sort de ces cas simples, les alternatives à cas spéciaux deviennent activement trompeuses.

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

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

et

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

33voto

Manikanda raj S Points 1154

J'aime le espace de noms mieux pour ce genre d'objectif.

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 constantes codées en dur.

31voto

Ciro Santilli Points 3341

C++17 inline variables

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

  • utiliser commodément une seule adresse mémoire pour chaque constante
  • le stocker comme un constexpr : Comment déclarer constexpr extern ?
  • le faire en une seule ligne à partir d'un seul 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;
}

Compilez et exécutez :

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 en ligne ou une variable avec lien externe doit avoir la même adresse dans toutes les unités de traduction.

Référence cpp https://en.cppreference.com/w/cpp/language/inline explique que si static n'est pas donné, alors il a un lien externe.

Mise en œuvre des variables en ligne

Nous pouvons observer comment il est mis en œuvre 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 mondial unique. Il s'agit d'une extension GNU de l'ensemble standard des liaisons de symboles ELF. Pour un tel symbole, l'éditeur de liens dynamiques s'assurera que dans tout le processus il n'y ait qu'un seul symbole de ce nom et de ce type utilisé.

nous voyons donc qu'il y a une extension ELF dédiée pour cela.

Projet de norme C++17 sur le terme "global". const implique static

C'est la citation de ce qui a été mentionné à : https://stackoverflow.com/a/12043198/895245

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

3 Un nom ayant la portée d'un espace de nom (6.3.6) a un lien interne s'il est le nom de

  • (3.1) - une variable, une fonction ou un modèle de fonction qui est explicitement déclaré statique ; ou,
  • (3.2) - une variable non en ligne de type qualifié const non volatile qui n'est ni explicitement déclarée extern ni déclarée précédemment comme ayant un lien externe ; ou
  • (3.3) - une donnée membre d'une union anonyme.

La portée de l'"espace de noms" est ce que nous appelons familièrement "global".

Annexe C (informative) Compatibilité, C.1.2 La clause 6 : "concepts de base" donne la raison pour laquelle cela a été changé de C :

6.5 [également 10.1.7]

Le changement : Un nom de fichier qui est explicitement déclaré const, et non explicitement déclaré extern, a un lien interne, alors qu'en C, il aurait un lien externe. 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é incite les programmeurs à fournir des informations sur les valeurs. les programmeurs à fournir un initialisateur explicite pour chaque objet const. Cette fonctionnalité permet à l'utilisateur de mettre des objets const dans des fichiers sources qui sont inclus dans plusieurs unités de traduction.

Effet sur la caractéristique originale : Modification de la sémantique d'une caractéristique bien définie.

Difficulté de la conversion : Transformation sémantique.

Largement utilisé : Rarement.

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

Testé dans GCC 7.4.0, Ubuntu 18.04.

28voto

Joachim Pileborg Points 121221

En général, vous ne devriez pas utiliser e.g. const int dans un fichier d'en-tête, s'il est inclus dans plusieurs fichiers sources. C'est parce qu'alors les variables seront définies une fois par fichier source (unités de traduction techniquement parlant) car mondial const les variables sont implicitement statiques en prenant 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, et ensuite avoir les variables déclarées en tant que 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;

2voto

arasmussen Points 5752

Plutôt que de créer un tas de variables globales, vous pouvez envisager de créer une classe qui possède un tas de constantes statiques publiques. La variable reste globale, mais de cette façon, elle est enveloppée dans une classe, ce qui vous permet de savoir d'où vient la constante et qu'elle est censée être une constante.

Constantes.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

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

#endif

Constantes.cpp

#include "Constants.h"

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

Ensuite, vous pouvez l'utiliser comme ça :

#include "Constants.h"

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X