web-dev-qa-db-fra.com

Variable statique du modèle

Je ne comprends pas pourquoi, si nous définissons la variable statique de la classe habituelle (non-template) dans l'en-tête, nous avons une erreur de l'éditeur de liens, mais dans le cas des modèles, tout fonctionne bien et de plus, nous aurons une seule instance de variable statique parmi toutes les unités de traduction. :

C'est l'en-tête du template (template.h):

// template.h
template<typename T>
class Templ {
public:
  static int templStatic;
};

template<typename T> Templ<T>::templStatic = 0;

C'est la première unité utilisant template (unit1.cpp)

// unit1.cpp
#include "template.h"

int method1() {
  return Templ<void>::templStatic++;
}

Deuxième unité ici (unit2.cpp):

// unit2.cpp
#include "template.h"
int method2() {
  return Templ<void>::templStatic++;
}

Et enfin, main.cpp:

// main.cpp
#include <iostream>
int method1();
int method2();

int main(int argc, char** argv) {
  std::cout << method1() << std::endl;
  std::cout << method2() << std::endl;
}

Après avoir compilé, lié et exécuté ce code, nous aurons le résultat suivant:

0
1

Alors, pourquoi dans le cas de modèles tout fonctionne bien (et comme prévu)? Comment le compilateur ou l'éditeur de liens gère-t-il cela (nous pouvons compiler chaque fichier .cpp en appelant séparément le compilateur, puis les lier avec caling à l'éditeur de liens, de sorte que le compilateur et l'éditeur de liens ne "voient" pas tous les fichiers .cpp en même temps)?

PS: Mon compilateur: msvcpp 9 (mais vérifié aussi avec mingw)

55
cybevnm

C'est parce que la définition du membre de données statique est elle-même un modèle. Cela est nécessaire pour la même raison que vous êtes autorisé à avoir un modèle de fonction qui n'est pas en ligne plusieurs fois dans un programme. Vous avez besoin du modèle pour générer l'entité résultante (une fonction ou un membre de données statique, par exemple). Si vous ne pouviez pas définir la définition d'un membre de données statique, comment instancieriez-vous les éléments suivants? 

template<typename T>
struct F {
  static int const value;
};

template<typename T>
int const F<T>::value = sizeof(T);

La variable T est inconnue - la norme indique que la définition en dehors du modèle de classe est une définition de modèle, dans laquelle les paramètres sont hérités du propriétaire de son modèle de classe. 


J'ai fait des expériences avec GCC. Dans ce qui suit, nous avons une instanciation implicite de F<float>::value et une spécialisation explicite de F<char>::value qui doit être définie dans un fichier .cpp pour ne pas provoquer d'erreur de symbole dupliqué lors de son inclusion multiple. 

// Translation Unit 1
template<typename T>
struct F {
  static int value; 
};

template<typename T>
int F<T>::value = sizeof(T);

// this would belong into a .cpp file
template<> int F<char>::value = 2;

// this implicitly instantiates F<float>::value
int test = F<float>::value;

int main() { }

La deuxième unité de traduction contient simplement une autre instanciation implicite du même membre de données statique 

template<typename T>
struct F {
  static int value; 
};

template<typename T>
int F<T>::value = sizeof(T);

int test1 = F<float>::value;

Voici ce que nous obtenons avec GCC: chaque instanciation implicite est transformée en un symbole faible et insérée dans une section distincte. Les symboles faibles ne causeront pas d'erreurs s'il en existe plusieurs au moment de la liaison. Au lieu de cela, l'éditeur de liens choisit une instance et élimine les autres en supposant qu'elles sont toutes identiques.

objdump -Ct main1.o # =>
# cut down to the important ones
00000000 l    df *ABS*  00000000 main1.cpp
0000000a l     F .text  0000001e __static_initialization_and_destruction_0(int, int)
00000000 l    d  .data._ZN1FIfE5valueE  00000000 .data._ZN1FIfE5valueE
00000028 l     F .text  0000001c global constructors keyed to _ZN1FIcE5valueE
00000000 g     O .data  00000004 F<char>::value
00000000 g     O .bss   00000004 test
00000000 g     F .text  0000000a main
00000000  w    O .data._ZN1FIfE5valueE  00000004 F<float>::value

Comme nous pouvons le constater, F<float>::value est un symbole faible, ce qui signifie que l’éditeur de liens peut en afficher plusieurs au moment de la liaison. test, main et F<char>::value sont des symboles globaux (non faibles). En reliant main1.o et main2.o ensemble, nous voyons dans la sortie de la carte (-Wl,-M) ce qui suit

# (mangled name)
.data._ZN1FIfE5valueE
    0x080497ac        0x4 main1.o                                             
    0x080497ac                F<float>::value

Cela indique qu'en réalité, il supprime tout sauf une instance.

60

Il existe une solution, vous pouvez créer une classe parente et y mettre la variable statique, puis faire en sorte que votre classe de modèle en hérite de manière privée. Voici un exemple:

class Parent
{
protected: 
    static long count;
};

long Parent::count = 0;

template<typename T>
class TemplateClass: private Parent
{
private: 
    int mKey;
public:
    TemplateClass():mKey(count++){}
    long getKey(){return mKey;}
}

int main()
{
    TemplateClass<int> obj1;
    TemplateClass<double> obj2;

    std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
    std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;

    return 0;
}

La sortie sera:

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

C'est parce que le code de modèle n'est pas le code source; ce sont des instructions sur la façon d'écrire le code source.

La variable statique non-template est le code source réel, et le compilateur essaiera de faire exactement ce que vous dites en incluant quelque chose deux fois. Par conséquent, vous devez initialiser la variable statique dans un fichier .cpp et ne la référencer que dans le fichier .h décrivant la classe. Cela équivaut à une variable globale déclarée par extern.

Quand le compilateur voit 

template<class T> Templ{...};

il ne fait rien sauf noter que le modèle existe. En ce qui le concerne, aucun code source n’est associé à Templ . La première fois que vous faites référence à 

Templ<int> Instance

le compilateur examine tout le code de modèle <> associé à Templ et l'utilise pour construire un fichier .h et un fichier .cpp (qui n'existe que pour la durée de la compilation). Ces fichiers peuvent ressembler à ceci:

Temple_int.h
class Templ_int{
  public:
  static int templStatic;
};

Templ_int.cpp
#include "Templ_int.h"
Templ_int::templStatic = 0;

Et chaque 

Templ<int>

devient un Templ_int . Ainsi, le code source pour initialiser la variable statique n’existe qu’une seule fois, dans un fichier .cpp créé par le compilateur . créer une classe avec un nom similaire au modèle, etc.)

0
user1167758