web-dev-qa-db-fra.com

variables constantes ne fonctionnant pas dans l'en-tête

si je définis mes variables constantes dans mon en-tête comme ceci ...

extern const double PI = 3.1415926535;
extern const double PI_under_180 = 180.0f / PI;
extern const double PI_over_180 = PI/180.0f;

Je reçois l'erreur suivante

1>MyDirectX.obj : error LNK2005: "double const PI" (?PI@@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_under_180" (?PI_under_180@@3NB) already defined in main.obj
1>MyDirectX.obj : error LNK2005: "double const PI_over_180" (?PI_over_180@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI" (?PI@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_under_180" (?PI_under_180@@3NB) already defined in main.obj
1>MyGame.obj : error LNK2005: "double const PI_over_180" (?PI_over_180@@3NB) already defined in main.obj

mais si je supprime ces constantes de l'en-tête et les mets dans le document qui inclut l'en-tête comme ceci.

const double PI = 3.1415926535;
const double PI_under_180 = 180.0f / PI;
const double PI_over_180 = PI/180.0f;

Ça marche

Quelqu'un a-t-il une idée de ce que je pourrais faire de mal ??

Merci

53
numerical25

Le problème est que vous define objets avec une liaison externe dans le fichier d’en-tête. Comme prévu, une fois que vous avez inclus ce fichier d'en-tête dans plusieurs unités de traduction, vous obtiendrez plusieurs définitions du même objet avec une liaison externe, ce qui constitue une erreur.

La bonne façon de le faire dépend de votre intention.

  1. Vous pouvez placer vos définitions dans le fichier d'en-tête, mais assurez-vous qu'elles ont un lien internal

    En C, cela nécessiterait une static explicite

    static const double PI = 3.1415926535; 
    static const double PI_under_180 = 180.0f / PI; 
    static const double PI_over_180 = PI/180.0f; 
    

    En C++, static est facultatif (car en C++, les objets const ont une liaison interne par défaut)

    const double PI = 3.1415926535; 
    const double PI_under_180 = 180.0f / PI; 
    const double PI_over_180 = PI/180.0f; 
    
  2. Ou vous pouvez mettre de simples déclarations non définissantes dans le fichier d’en-tête et placer les définitions dans un (et un seul) fichier de mise en oeuvre.

    Les déclarations dans le fichier header doivent inclure un initialiseur explicite extern et no

    extern const double PI; 
    extern const double PI_under_180; 
    extern const double PI_over_180; 
    

    et les définitions dans un fichier implementation doivent ressembler à ceci:

    const double PI = 3.1415926535; 
    const double PI_under_180 = 180.0f / PI; 
    const double PI_over_180 = PI/180.0f; 
    

    (extern explicite dans les définitions est facultatif, si les déclarations ci-dessus précèdent les définitions dans la même unité de traduction).

La méthode que vous choisirez dépend de votre intention. 

La première méthode permet au compilateur d’optimiser le code plus facilement, car il peut voir la valeur réelle de la constante dans chaque unité de traduction. Mais en même temps, conceptuellement, vous obtenez des objets constants séparés et indépendants dans chaque unité de traduction. Par exemple, &PI sera attribué à une adresse différente dans chaque unité de traduction.

La deuxième méthode crée de véritables constantes globales, c’est-à-dire des objets constants uniques partagés par l’ensemble du programme. Par exemple, &PI sera évalué à la même adresse dans chaque unité de traduction. Mais dans ce cas, le compilateur ne peut voir les valeurs réelles que dans une et une seule unité de traduction, ce qui pourrait gêner les optimisations.


À partir de C++ 17, vous obtenez la troisième option, qui combine en quelque sorte "le meilleur des deux mondes": variables inline. Les variables en ligne peuvent être définies en toute sécurité dans les fichiers d’en-tête malgré le couplage externe

inline extern const double PI = 3.1415926535; 
inline extern const double PI_under_180 = 180.0f / PI; 
inline extern const double PI_over_180 = PI/180.0f; 

Dans ce cas, vous obtenez un objet constant nommé dont la valeur d'initialisation est visible dans toutes les unités de traduction. Et en même temps, l’objet a un lien externe, c’est-à-dire qu’il a une identité d’adresse globale (&PI est le même dans toutes les unités de traduction).

Certes, une telle chose pourrait n'être nécessaire que pour des objectifs exotiques (la plupart des cas d'utilisation en C++ appellent pour la première variante), mais la fonctionnalité existe.

125
AnT

extern signifie que la définition "réelle" de la variable est ailleurs et que le compilateur doit avoir confiance que les choses se connecteront au moment de la liaison. Avoir la définition en ligne avec extern est bizarre et c'est ce qui modifie votre programme. Si vous voulez qu'ils soient extern, définissez-les simplement exactement une fois ailleurs dans votre programme.

9
Carl Norum

La classe de stockage extern pour eux est presque certainement la cause du problème que vous rencontrez. Si vous le supprimez, le code ira probablement bien (du moins à cet égard).

Edit: Je viens de remarquer que vous avez étiqueté ceci en C et C++. À cet égard, C et C++ sont vraiment très différents (mais d'après les messages d'erreur, vous compilez apparemment en C++, pas en C). En C++, vous souhaitez supprimer la variable extern, car (par défaut) les variables const ont la classe de stockage static. Cela signifie que chaque fichier source (unité de traduction) aura sa propre "copie" de la variable et qu'il n'y aura pas de conflit entre les définitions de différents fichiers. Puisque vous utilisez (probablement) uniquement les valeurs, sans les traiter comme des variables, avoir plusieurs "copies" ne fera pas de mal - aucun d'entre eux ne se verra attribuer d'espace de stockage.

En C, extern est assez différent, et supprimer extern ne fera aucune différence réelle, car ils seront extern par défaut. Dans ce cas, vous devez vraiment initialiser les variables à un endroit précis et les déclarer externes dans l'en-tête. Vous pouvez également ajouter la classe de stockage static que C++ ajoutera par défaut si/si vous supprimez la extern de l'en-tête.

5
Jerry Coffin

Beaucoup de réponses incorrectes ci-dessous. Ceux qui sont corrects sont ceux qui vous disent de supprimer la extern, comme Sellibitze l’a également dit dans son commentaire. 

Comme ils sont déclarés const, il n’ya aucun problème à avoir la définition dans l’en-tête. C++ incorporera un const pour un type prédéfini, sauf si vous essayez de prendre son adresse (un pointeur sur un const), auquel cas il l'instanciera avec une liaison static. Vous pourrez alors obtenir plusieurs instanciations dans des modules séparés. tous les pointeurs sur le même const ayant la même adresse, ce n'est pas un problème.

2
Clifford

Le problème est que vous initialisez les variables dans le fichier d’en-tête; cela crée une déclaration define, qui est répétée dans chaque fichier contenant cet en-tête, d'où l'erreur de définition multiple.

Vous voulez une déclaration non -defining (pas d'initialiseur) dans le fichier d'en-tête et placez la déclaration de définition dans one des fichiers d'implémentation.

2
John Bode

Vous devez déclarer les contants dans l'en-tête, puis les définir dans l'un de vos fichiers de code. Si vous ne les déclarez nulle part, il existe une erreur de l'éditeur de liens lorsqu'il essaie de lier la déclaration à la définition réelle. Vous pouvez également vous en tirer en utilisant les instructions #ifdef pour avoir une définition dans l'en-tête.

Assurez-vous qu'ils sont déclarés dans un en-tête inclus par tous ceux qui en ont besoin et assurez-vous qu'ils sont définis exactement une fois.

Jacob

1
TheJacobTaylor

Si vous souhaitez définir des constantes dans les fichiers d'en-tête, utilisez static const. Si vous utilisez extern, l'éditeur de liens a raison de se plaindre de plusieurs définitions, car chaque fichier source inclus fournira de la mémoire pour la variable si vous affectez une valeur.

1
Christoph

Il semble que ce fichier d'en-tête soit inclus plusieurs fois. Vous devez ajouter des gardes.

En haut de chaque fichier d'en-tête, vous devriez avoir quelque chose comme:

#ifndef MY_HEADER_FILE_NAME_H
#define MY_HEADER_FILE_NAME_H

...

// at end of file
#endif

Si vous utilisez g ++ ou MSVC, vous pouvez simplement ajouter:

#pragma once

En haut de chaque fichier d'en-tête, mais ce n'est pas 100% portable.

De plus, vous ne devriez pas définir de constantes dans les fichiers d’en-tête, mais seulement les déclarer:

// In header file
extern const int my_const;


// In one source file
const int my_const = 123;
1
Peter Alexander

Une vieille question, certes, mais une réponse utile manque.

Il est possible de persuader MSVC d'accepter des constantes statiques dans les en-têtes en les enveloppant simplement dans un modèle de classe "factice":

template <typename Dummy = int>
struct C {
     static const double Pi;
};

template <typename Dummy = int>
const double C<Dummy>::Pi = 3.14159;

Maintenant, C <> :: PI est accessible d’ailleurs. Aucune redéfinition ne se plaint; constant est directement accessible dans chaque unité de compilation sans optimisation de temps de liaison sophistiquée. Les macros peuvent être déployées pour améliorer cette approche (même si les macros sont diaboliques).

0
oakad

en déclarant const global dans l'en-tête, chaque unité de compilation incluant ce hader aura ses propres définitions, définitions globales portant le même nom . Ensuite, l'éditeur de liens n'aime pas cela.

Si vous en avez vraiment besoin dans l'en-tête, alors vous devriez probablement les déclarer comme statiques. 

0
lollinus