2010-05-09 13 views
13

Ein Kollege von mir erzählte mir von einem kleinen Stück Design, das er mit seinem Team benutzt hatte, das meine Gedanken zum Kochen brachte. Es ist eine Art von Traits Klasse, die sie in einer extrem entkoppelten Art und Weise spezialisieren können.Nicht definierte Vorlagenmethoden Trick?

Ich hatte eine harte Zeit zu verstehen, wie es möglicherweise funktionieren könnte, und ich bin immer noch unsicher über die Idee, die ich habe, also dachte ich, ich würde hier um Hilfe bitten.

Wir sprechen hier g ++, speziell die Versionen 3.4.2 und 4.3.2 (es scheint mit beiden zu funktionieren).

Die Idee ist ganz einfach:

1- die Schnittstelle definieren

// interface.h 
template <class T> 
struct Interface 
{ 
    void foo(); // the method is not implemented, it could not work if it was 
}; 

// 
// I do not think it is necessary 
// but they prefer free-standing methods with templates 
// because of the automatic argument deduction 
// 
template <class T> 
void foo(Interface<T>& interface) { interface.foo(); } 

2- eine Klasse definieren, und in der Quelldatei für diese Klasse die Schnittstelle spezialisiert sind (Definition ihrer Methoden)

// special.h 

class Special {}; 


// special.cpp 

#include "interface.h" 
#include "special.h" 

// 
// Note that this specialization is not visible outside of this translation unit 
// 
template <> 
struct Interface<Special> 
{ 
    void foo() { std::cout << "Special" << std::endl; } 
}; 

3- nutzen zu können, ist es zu einfach:

// main.cpp 

#include "interface.h" 

class Special; // yes, it only costs a forward declaration 
       // which helps much in term of dependencies 

int main(int argc, char* argv[]) 
{ 
    Interface<Special> special; 
    foo(special); 
    return 0; 
}; 

Es ist ein undefiniertes Symbol, wenn keine Übersetzungseinheit eine Spezialisierung von Interface für Special definiert hat.

Jetzt hätte ich gedacht, dass dies das Schlüsselwort export erfordern würde, das meines Wissens nie in g ++ implementiert wurde (und nur einmal in einem C++ - Compiler implementiert wurde, wobei seine Autoren niemanden dazu ermahnten, angesichts der Zeit und des Aufwands es nahm sie).

Ich vermute, es hat etwas mit dem Linker Lösung der Vorlagen Methoden zu tun ...

  • Haben Sie jemals etwas Derartiges schon einmal getroffen?
  • Entspricht es dem Standard oder glauben Sie, es ist ein glücklicher Zufall, dass es funktioniert?

Ich muss ich durch das Konstrukt bin ganz verwirrt zugeben ...

Antwort

10

Wie @Steward vermutet, ist es nicht gültig.Formal ist es effektiv verursacht undefiniertes Verhalten, weil der Standard Regeln, dass für eine Verletzung keine Diagnose erforderlich ist, was bedeutet, dass die Implementierung kann nichts tun, was sie will. Bei 14.7.3/6

Wenn eine Vorlage, ein Mitglied Vorlage oder das Mitglied einer Klassenvorlage explizit spezialisierte dann die Spezialisierung wird vor der ersten Verwendung dieser Spezialisierung erklärt werden, die eine implizite Instanziierung verursachen würde stattfinden, in jedem Übersetzungseinheit, in der eine solche Verwendung stattfindet; keine Diagnose ist erforderlich.

In der Praxis zumindest auf GCC, es instanziieren implizit die primäre Vorlage Interface<T> da die Spezialisierung nicht erklärt wurde, und ist nicht sichtbar in main, und dann Interface<T>::foo aufrufen. Wenn seine Definition sichtbar ist, setzt sie die primäre Definition der Elementfunktion in Gang (weshalb sie, wenn sie definiert ist, nicht funktionieren würde).

Instanziierte Funktionsnamensymbole haben eine schwache Verknüpfung, weil sie möglicherweise in verschiedenen Objektdateien mehrfach vorhanden sind und im endgültigen Programm zu einem Symbol zusammengeführt werden müssen. Im Gegensatz dazu haben Mitglieder expliziter Spezialisierungen, die keine Templates mehr sind, starke Verknüpfungen, so dass sie schwache Verknüpfungssymbole dominieren und den Anruf in der Spezialisierung enden lassen. All dies ist ein Implementierungsdetail, und der Standard hat keine solche Vorstellung von einer schwachen/starken Verknüpfung. Sie müssen die Spezialisierung erklären vor der Erstellung des special Objekt:

template <> 
struct Interface<Special>; 

Die Norm legt sie kahl (betonen von mir)

Die Platzierung der expliziten Spezialisierung Erklärungen für Funktionsschablonen, Klassenvorlagen, Mitglied Funktionen von Klassenvorlagen, statische Datenelemente von Klassenvorlagen, Mitgliederklassen von Klassenvorlagen, Mitgliederklassenvorlagen von Klassenvorlagen, Mitgliederfunktionsvorlagen von Klassenvorlagen, Mitgliederfunktionen von Mitgliedervorlagen von Klassenvorlagen, Mitgliederfunktionen von Mitgliedervorlagen von Nichtvorlagen Klassen, Mitgliederfunktionsvorlagen von Mitgliedsklassen von c Lass-Vorlagen usw. und die Platzierung von Teilspezialisierungsdeklarationen von Klassenvorlagen, Mitgliederklassenvorlagen von Nicht-Vorlagenklassen, Mitgliederklassenvorlagen von Klassenvorlagen usw. können beeinflussen, ob ein Programm gemäß der relativen Positionierung wohlgeformt ist der expliziten Spezialisierungsdeklarationen und ihrer Instanziierungspunkte in der Übersetzungseinheit wie oben und unten angegeben. Wenn Sie eine Spezialisierung schreiben, seien Sie vorsichtig bei der Lokalisierung. oder um es kompilieren zu lassen, wird so ein Versuch sein, seine Selbstverbrennung anzuzünden.

+0

Also wenn ich richtig verstehe, würde es ausreichen in "special.h" "interface.h" einzuschließen und die Template-Spezialisierung 'template <> struct Interface ;' zu deklarieren und dann in "main.cpp" einzuschließen "special.h", um das Programm wohlgeformt zu machen, obwohl die Definition der 'Interface :: foo' Methode nie erscheinen würde? –

+0

@Matthieu, genau!So können Sie das Elend lösen –

+0

Wenn ich das richtig lese, bedeutet das, dass jedes Programm (mehrere Übersetzungseinheiten) eine explizite Spezialisierung einer Vorlage für einen gegebenen Satz von Parametern enthält, aber in einer anderen Übersetzungseinheit wird die implizite Spezialisierung verwendet Derselbe Parametersatz ist eine ODR-Verletzung, die der Compiler nicht benötigt - und wahrscheinlich nicht kann -, um eine Diagnose zu erstellen. Das ist ziemlich gruselig und ein weiterer guter Grund, immer Teilspezialisierungen mit der primären Vorlage immer wenn möglich zu platzieren. – Stewart

5

Das ist recht ordentlich. Ich bin mir nicht sicher, ob es garantiert überall funktioniert. Es sieht so aus, als hätten sie eine bewusst undefinierte Vorlagenmethode und definieren dann eine Spezialisierung, die in einer eigenen Übersetzungseinheit versteckt ist. Sie hängen davon ab, dass der Compiler denselben Namen verwendet, der sowohl für die ursprüngliche Klassenschablonenmethode als auch für die Spezialisierung fehlt, was meiner Meinung nach wahrscheinlich nicht standardgemäß ist. Der Linker wird dann nach der Methode der Klassenvorlage suchen, aber stattdessen die Spezialisierung finden.

Es gibt jedoch ein paar Risiken mit diesem. Niemand, nicht einmal der Linker, wird zum Beispiel mehrere Implementierungen der Methode aufgreifen. Die Template-Methoden werden als selectany markiert, da template imply bedeutet, dass wenn der Linker mehrere Instanzen sieht, anstatt einen Fehler auszugeben, dieser den bequemsten auswählt.

Immer noch ein schöner Trick, obwohl es leider ein glücklicher Zufall scheint, dass es funktioniert.

+0

Nun, die Verknüpfung Problem entsteht für jede Definition Template-Methode, auch wenn das Risiko mit Spezialisierung höher ist gebe ich zu. Hier verlassen sie sich auf Konvention (die Spezialisierung befindet sich in der entsprechenden Quelldatei), um einen Verstoß gegen die Eine-Definition-Regel zu verhindern. Man könnte anmerken, dass, wenn die Template-Methode für den generischen Fall definiert würde, ODR verletzt würde, weil die generische Vorlage instanziert wäre. Guter Punkt auf dem Namen Mangling Thema, es war mir nicht eingefallen, da könnte es auch ein Problem geben. –