2009-10-12 9 views
48

Je ne peux pas comprendre, pourquoi si nous définissons la variable statique de la classe habituelle (non-modèle) dans l'en-tête, nous avons une erreur de lien, mais dans le cas de modèles tout fonctionne bien et nous aurons une instance unique variable statique entre toutes les unités de traduction:Modèle variable statique

Il est tête de modèle (template.h):

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

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

Il est la première unité modèle à l'aide (Unit1.cpp)

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

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

Deuxième unité il re (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 compilling, la liaison et l'exécution de ce code, nous avons sortie suivante:

0 
1 

Alors, pourquoi dans le cas de modèles tout fonctionne bien (et comme prévu)? Comment compilateur ou éditeur de liens gérer cela (nous pouvons compiler chaque fichier. Cpp dans l'appel séparé du 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.

PS: Mon compilateur: msvcpp 9 (mais nous sommes sur MinGW aussi)

+0

Il serait plus utile de nous montrer le code ** qui ne fonctionne pas. – JesperE

+0

Je suppose que le code qui ne fonctionne pas est celui où vous définissez une variable dans un en-tête qui est incluse dans plus d'un fichier (non externé), ce qui entraîne une collision de nommage. – falstro

Répondre

54

C'est parce que la définition de l'élément de données statiques lui-même est un modèle. Autoriser 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 (par exemple, une fonction ou un membre de données statiques). Si vous n'êtes pas autorisé à mettre la définition d'un membre de données statiques, comment vous instancier les éléments suivants

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

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

On ne sait pas ce que T est - la norme indique la définition en dehors du modèle de classe est une définition de modèle , dans lequel les paramètres sont hérités de son propriétaire de modèle de classe.


J'ai fait quelques 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'erreurs de symboles dupliquées lorsqu'elles sont incluses plusieurs fois.

// 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 juste une autre instanciation implicite du même membre de données statiques

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 - il fait chaque instanciation implicite dans un des symboles faibles et des bâtons dans sa propre section ici. Les symboles faibles ne provoquent pas d'erreurs lorsqu'il existe plusieurs d'entre eux au moment de la liaison. Au lieu de cela, l'éditeur de liens choisira une instance et rejette les autres en supposant tous sont les mêmes

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 

Alors que nous pouvons voir F<float>::value est un symbole faible, ce qui signifie que l'éditeur de liens peut voir plusieurs de ces au moment de la liaison .test, main et F<char>::value sont des symboles globaux (non-faibles). Lier main1.o et main2.o ensemble, nous voyons dans la sortie de la carte (-Wl,-M) les éléments suivants

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

Cela indique que réellement gouttes tous, sauf un cas.

+0

Ok. Mais comment éditeur de liens, qui voient deux "modèle Templ :: templStatic = 0;" définitions (dans unit1.cpp et unit2.cpp) gérer cette situation? Est-ce que les fichiers objets ont des méta-informations spécifiques au C++, qui peuvent dire au linker, qu'une définition peut être ignorée (et, par conséquent, nous n'avons pas d'erreur de lien "multiple definitions")? – cybevnm

+0

ajouté quelques trucs GCC –

1

Il y a une solution, vous pouvez créer une classe parente et de mettre la variable statique, puis faites hériter votre classe de modèle en privé, 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; 
} 

sortie sera:

Object 1 key is: 0 
Object 2 key is: 1 
Questions connexes