wake-up-neo.com

Statische Variable der Vorlage

Ich kann nicht verstehen, warum wir Linker-Fehler haben, wenn wir eine statische Variable einer üblichen (nicht Template) -Klasse definieren, aber im Falle von Templates funktioniert alles einwandfrei und außerdem haben wir eine einzige Instanz einer statischen Variable unter allen Übersetzungseinheiten :

Es ist ein Template-Header (template.h):

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

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

Es ist die erste Einheit, die eine Vorlage verwendet (unit1.cpp)

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

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

Zweite Einheit hier (unit2.cpp):

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

Und schließlich 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;
}

Nachdem Sie diesen Code erstellt, verlinkt und ausgeführt haben, erhalten Sie folgende Ausgabe:

0
1

Warum funktioniert bei Templates alles einwandfrei (und wie erwartet)? Wie können Compiler oder Linker damit umgehen (wir können jede .cpp-Datei im separaten Aufruf des Compilers kompilieren und sie dann mit dem Linking verknüpfen, sodass Compiler und Linker nicht alle CPP-Dateien zur gleichen Zeit "sehen")?

PS: Mein Compiler: msvcpp 9 (aber auch bei mingw geprüft)

55
cybevnm

Dies liegt daran, dass die Definition des statischen Datenmembers selbst eine Vorlage ist. Dies zuzulassen ist aus dem gleichen Grund erforderlich, aus dem Sie eine Funktionsvorlage haben dürfen, die nicht mehrmals in einem Programm Inline enthalten ist. Sie benötigen die Vorlage, um die resultierende Entität (z. B. eine Funktion oder ein statisches Datenelement) zu generieren. Wenn Sie die Definition eines statischen Datenelements nicht definieren dürfen, wie würden Sie Folgendes instanziieren? 

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

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

Es ist nicht bekannt, was T ist - der Standard sagt, dass die Definition außerhalb der Klassenvorlage eine Vorlagendefinition ist, in der die Parameter von ihrem Klassenvorlageneigentümer vererbt werden. 


Ich habe ein paar Experimente mit GCC gemacht. Im Folgenden haben wir eine implizite Instantiierung von F<float>::value und eine explizite Spezialisierung von F<char>::value, die in einer .cpp-Datei definiert werden muss, um doppelte Symbolfehler nicht zu verursachen, wenn sie mehrfach eingefügt werden. 

// 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() { }

Die zweite Übersetzungseinheit enthält nur eine weitere implizite Instantiierung desselben statischen Datenelements 

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

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

int test1 = F<float>::value;

Das bekommen wir mit GCC - es macht jede implizite Instantiierung zu schwachen Symbolen und steckt sie hier in einen eigenen Abschnitt. Schwache Symbole verursachen keine Fehler, wenn zum Zeitpunkt der Verbindung mehrere vorhanden sind. Stattdessen wählt der Linker eine Instanz aus und verwirft die andere Instanz, vorausgesetzt, dass alle gleich sind

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

Wie wir sehen können, ist F<float>::value ein schwaches Symbol, was bedeutet, dass der Linker zur Linkzeit mehrere davon sehen kann. test, main und F<char>::value sind globale (nicht schwache) Symbole. Wenn Sie main1.o und main2.o miteinander verknüpfen, sehen wir in der Map-Ausgabe (-Wl,-M) Folgendes

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

Dies zeigt an, dass tatsächlich alle außer einer Instanz gelöscht werden.

Es gibt eine Lösung. Sie können eine übergeordnete Klasse erstellen und die statische Variable darin einfügen. Anschließend wird Ihre Vorlagenklasse privat erben. Hier ein Beispiel:

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;
}

Ausgabe wird sein:

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

Dies liegt daran, dass Vorlagencode kein Quellcode ist. Es gibt Anweisungen zum Schreiben von Quellcode.

Die statische Variable, die keine Vorlage ist, ist tatsächlicher Quellcode, und der Compiler versucht genau das zu tun, was Sie sagen, indem er etwas zweimal einfügt. Daher müssen Sie die statische Variable in einer .cpp-Datei initialisieren und nur in der .h-Datei referenzieren, die die Klasse beschreibt. Sie entspricht einer durch extern deklarierten globalen Variablen.

Wenn der Compiler sieht 

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

es macht nichts außer eine Notiz, dass die Vorlage existiert. Templ ..__ ist kein Quellcode zugeordnet, auf den Sie sich zum ersten Mal beziehen 

Templ<int> Instance

der Compiler betrachtet den mit Templ verknüpften Code der Vorlage <> und erstellt daraus eine .h- und eine .cpp-Datei (die nur für die Dauer der Kompilierung existiert). Diese Dateien könnten so aussehen:

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

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

Und jeder 

Templ<int>

wird zu einem Templ_int . Daher existiert der Quellcode zum Initialisieren der statischen Variablen nur einmal in einer vom Compiler erstellten .cpp-Datei ..__ Erstellen einer Klasse mit einem ähnlichen Namen wie die Vorlage usw.)

0
user1167758