web-dev-qa-db-fra.com

Référence indéfinie à static const int

J'ai rencontré un problème intéressant aujourd'hui. Considérez cet exemple simple:

template <typename T>
void foo(const T & a) { /* code */ }

// This would also fail
// void foo(const int & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

int main()
{
   Bar b;
   b.func();
}

Lors de la compilation, j'obtiens une erreur:

Undefined reference to 'Bar::kConst'

Maintenant, je suis à peu près sûr que c'est parce que le static const int n'est défini nulle part, ce qui est intentionnel car, d'après ce que j'ai compris, le compilateur devrait pouvoir effectuer le remplacement au moment de la compilation et ne pas nécessiter de définition. Toutefois, comme la fonction utilise un paramètre const int &, elle ne semble pas effectuer la substitution mais préfère une référence. Je peux résoudre ce problème en apportant les modifications suivantes:

foo(static_cast<int>(kConst));

Je crois que cela oblige maintenant le compilateur à créer un int temporaire, puis à lui transmettre une référence, ce qu'il peut faire avec succès au moment de la compilation.

Je me demandais si c'était intentionnel ou si j'attendais trop de gcc pour pouvoir gérer ce cas? Ou est-ce quelque chose que je ne devrais pas faire pour une raison quelconque?

67
JaredC

C'est intentionnel, 9.4.2/4 dit:

Si un membre de données statique est de type énum intégrale ou const constant, sa déclaration dans la classe définition peut spécifier un initialiseur constant qui doit être un expression constante intégrale (5.19) In Dans ce cas, le membre peut apparaître dans expressions constantes intégrales. Le membre doit toujours être défini dans un Portée de l'espace de noms si elle est utilisée dans. programme

Lorsque vous passez le membre de données statique par référence const, vous "l'utilisez", 3.2/2:

Une expression est potentiellement évaluée sauf s'il apparaît où une intégrale une expression constante est requise (voir 5.19), est l'opérande de l'opérateur sizeof (5.3.3) ou l'opérande de l'opérateur typeid et l'expression ne désigne pas une valeur de type de classe polymorphe (5.2.8). Un objet ou fonction non surchargée est utilisé si son nom apparaît dans un expression potentiellement évaluée.

Donc, en fait, vous "utilisez" quand vous le transmettez aussi par valeur, ou dans un static_cast. C'est juste que GCC vous a laissé échapper dans un cas mais pas dans l'autre.

[Edit: gcc applique les règles de brouillons C++ 0x: "Une fonction variable ou non surchargée dont le nom apparaît en tant qu’expression potentiellement évaluée est odr-used sauf s’il s’agit d’un objet qui satisfait aux critères d’apparition dans une constante l'expression (5.19) et la conversion de valeur à valeur (4.1) est immédiatement appliquée. ". La conversion statique effectue immédiatement la conversion lvalue-rvalue, elle n'est donc pas "utilisée" en C++.]

Le problème pratique de la référence const est que foo a le droit de prendre l'adresse de son argument et de la comparer, par exemple, à l'adresse de l'argument d'un autre appel, stocké dans un global. Dans la mesure où un membre de données statique est un objet unique, cela signifie que si vous appelez foo(kConst) à partir de deux TU différentes, l'adresse de l'objet transmis doit être la même dans chaque cas. AFAIK GCC ne peut organiser cela que si l'objet est défini dans une (et une seule) TU.

OK, dans ce cas, foo est un modèle, donc la définition est visible dans toutes les UT. Le compilateur pourrait donc théoriquement exclure le risque de faire quoi que ce soit avec l'adresse. Mais en général, vous ne devriez certainement pas prendre d'adresses ou de références à des objets inexistants ;-)

57
Steve Jessop

Si vous écrivez une variable const statique avec un initialiseur dans la déclaration de classe, c'est comme si vous aviez écrit 

class Bar
{
      enum { kConst = 1 };
}

et GCC le traitera de la même manière, ce qui signifie qu’il n’a pas d’adresse.

Le code correct devrait être

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;
22
pelya

C'est un cas vraiment valable. Surtout parce que foo pourrait être une fonction du STL telle que std :: count qui prend un troisième argument: const T &.

J'ai passé beaucoup de temps à essayer de comprendre pourquoi l'éditeur de liens avait des problèmes avec un code aussi fondamental.

Le message d'erreur 

Référence non définie à 'Bar :: kConst'

nous dit que l'éditeur de liens ne peut pas trouver un symbole.

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst

On peut voir dans le 'U' que Bar :: kConst n'est pas défini. Par conséquent, lorsque l'éditeur de liens essaie de faire son travail, il doit trouver le symbole. Mais vous ne faites que declare kConst et ne le définissez pas.

La solution en C++ consiste également à le définir comme suit:

template <typename T>
void foo(const T & a) { /* code */ }

class Bar
{
public:
   static const int kConst = 1;
   void func()
   {
      foo(kConst);           // This is the important line
   }
};

const int Bar::kConst;       // Definition <--FIX

int main()
{
   Bar b;
   b.func();
}

Ensuite, vous pouvez voir que le compilateur mettra la définition dans le fichier objet généré:

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst

Maintenant, vous pouvez voir le «R» dire qu'il est défini dans la section de données.

12
Stac

g ++ version 4.3.4 accepte ce code (voir ce lien ). Mais g ++ version 4.4.0 le rejette.

2
TonyK

Vous pouvez également le remplacer par une fonction membre constexpr:

class Bar
{
  static constexpr int kConst() { return 1; };
};
1
Ben-Uri

Astuce simple: utilisez + avant la kConst transmise par la fonction. Cela empêchera que la constante soit prise en référence par une référence. Ainsi, le code ne générera pas de demande d’éditeur de liens vers l’objet constante, mais continuera avec la valeur constante du compilateur.

1
Ethouris

Je pense que cet artefact de C++ signifie que chaque fois que l'on fait référence à Bar::kConst, sa valeur littérale est utilisée à la place.

Cela signifie qu'en pratique, il n'y a pas de variable pour faire un point de référence.

Vous devrez peut-être faire ceci:

void func()
{
  int k = kConst;
  foo(k);
}
1
quamrana

J'ai rencontré le même problème que mentionné par Cloderic (const statique dans un opérateur ternaire: r = s ? kConst1 : kConst2), mais il ne s'est plaint qu'après avoir désactivé l'optimisation du compilateur (-O0 au lieu de -Os). Arrivé sur gcc-none-eabi 4.8.5.

0
Scg