Utiliser une variable statique avec des modèles

J’ai une classe de modèle définie dans un fichier d’en-tête comme celui-ci. Ici, j’ai également défini une variable statique:

#ifndef TEST1_H_ #define TEST1_H_ void f1(); static int count; template  class MyClass { public: void f() { ++count; } }; #endif 

Et j’ai défini la fonction main () dans un fichier cpp différent, comme ceci:

 int main(int argc, char* argv[]) { MyClass a; af(); f1(); cout<<"Main:" << count << "\n"; return 0; } 

J’ai implémenté la fonction f1 () dans un autre fichier cpp comme ceci:

 void f1() { MyClass a; af(); cout<<"F1: " <<count <<"\n"; } 

Quand j’ai compilé ceci en utilisant VC6, j’ai obtenu le résultat en tant que “F1: 0 Main: 2”. Comment est-ce possible? De même, en général, comment dois-je gérer si je veux utiliser des variables statiques avec des modèles?

Vous obtenez deux copies de la même variable parce que vous avez déclaré une variable statique dans un fichier d’en-tête. Lorsque vous déclarez une variable globale static cette manière, vous indiquez qu’elle est locale dans l’unité de compilation (le fichier .o ). Puisque vous incluez l’en-tête dans deux unités de compilation, vous obtenez deux copies de count .

Je pense que ce que vous voulez vraiment ici est une variable membre de modèle statique associée à chaque instance de la classe de modèle. Cela ressemblerait à ceci:

 template  class MyClass { // static member declaration static int count; ... }; // static member definition template int MyClass::count = 0; 

Cela vous donnera un compte pour chaque instanciation de votre modèle. Cela MyClass vous aurez un compte pour MyClass , MyClass , MyClass , etc. f1() ressemblerait maintenant à ceci:

 void f1() { MyClass a; af(); cout<<"F1: " << MyClass::count <<"\n"; } 

Si vous souhaitez un nombre pour toutes les instanciations de MyClass (quels que soient leurs parameters de modèle), vous devez utiliser une variable globale .

Cependant, vous ne voulez probablement pas de variable globale directement, car vous courez le risque de l’utiliser avant qu’elle ne soit initialisée. Vous pouvez contourner ce problème en créant une méthode statique globale qui renvoie une référence à votre nombre:

 int& my_count() { static int count = 0; return count; } 

Puis y accéder depuis votre classe comme ceci:

 void f() { ++my_count(); } 

Cela garantira que le nombre est initialisé avant son utilisation, quelle que soit l'unité de compilation à laquelle vous accédez. Voir la FAQ C ++ sur l'ordre d'initialisation statique pour plus de détails.

Si vous insérez la déclaration statique dans un fichier d’en-tête, chaque fichier .cpp aura sa propre version de la variable. Les deux déclarations de cout impriment donc des variables différentes.

Vous attendiez-vous à “F1: 1 Main: 1”? Vous avez instancié MyClass dans deux unités de traduction distinctes (c.-à-d. Deux fichiers objects), et l’éditeur de liens a constaté qu’il existait une instanciation de modèle dupliquée.

Passez-vous /OPT:ICF ou /OPT:REF à l’éditeur de liens VC6? Cela peut être lié à la suppression de l’instanciation de modèles en double (ou non; les instanciations de modèles en double peuvent constituer un cas particulier, par rapport aux fonctions de duplication ordinaires). GCC semble faire quelque chose de similaire sur certaines plates-formes.

Quoi qu’il en soit, je ne compterais pas sur la cohérence de ce comportement entre les compilateurs. En outre, la modification de l’ordre des fichiers object sur la ligne de commande de l’éditeur de liens peut avoir une incidence sur l’instanciation supprimée.

Il existe une autre solution: vous pouvez créer une classe parent partagée et y insérer cette variable statique, puis faire en sorte que votre classe de modèle l’hérite de manière privée. Voici un exemple:

 class Parent { protected: static long count; }; long Parent::count = 0; template class TemplateClass: private Parent { private: int mKey; public: TemplateClass():mKey(count++){} long getKey(){return mKey;} } int main() { TemplateClass obj1; TemplateClass obj2; std::cout<<"Object 1 key is: "< 

La sortie sera:

 Object 1 key is: 0 Object 2 key is: 1 

Je pense que c’est en fait un comportement indéfini .

Selon C ++ 14 [basic.def.odr] / 6:

Il peut exister plus d’une définition d’une fonction membre d’un modèle de classe dans un […] programme, à condition que chaque définition apparaisse dans une unité de traduction différente et que les définitions satisfassent aux exigences suivantes. Étant donné une telle entité nommée D définie dans plusieurs unités de traduction, alors

  • chaque définition de D doit consister en la même séquence de jetons; et
  • dans chaque définition de D, les noms correspondants, recherchés conformément à 3.4, doivent faire référence à une entité définie dans la définition de D ou à la même entité, après résolution de la surcharge (13.3) et après mise en correspondance de la spécialisation partielle du modèle (14.8 .3), sauf qu’un nom peut faire référence à un object const non volatile avec une liaison interne ou aucune liaison si l’object a le même type littéral dans toutes les définitions de D et qu’il est initialisé avec une expression constante (5.19), et l’object n’est pas utilisé et l’object a la même valeur dans toutes les définitions de D; […]

Le problème est que, dans le premier fichier .cpp , le count noms dans f1 fait référence à un object différent du nom de noms dans f1 dans le deuxième fichier .cpp , violant ainsi la condition selon laquelle les noms correspondants doivent faire référence à la même entité.

Ce sont des objects différents en raison du spécificateur static qui dit que chaque unité de traduction obtient son propre object avec ce nom.