2015-01-08 16 views
8

Ich habe eine Template-Klasse mit einer Spezialisierung, die in einer anderen Datei definiert ist. Daher ist es möglich, zwei Versionen derselben Klasse zu generieren: Einmal durch Ersetzen des Template-Parameters und einmal durch Verwenden der Spezialisierung. Mein derzeitiges Verständnis ist, dass dies dazu führen kann, dass zwei Instanzen des gleichen Typs unterschiedliche Größen im Speicher haben, was zu Segmentierungsfehlern führt.So finden Sie doppelte Definitionen aus Vorlagenspezialisierungen?

habe ich ein minimales Beispiel und der folgende Code ist die Frage zu veranschaulichen:

eine Template-Klasse erstellen:

// - templateexample.h --------------- 
#ifndef TEMPLATEEXAMPLE_H 
#define TEMPLATEEXAMPLE_H 
template<typename T> class Example 
{ 
    public: 
     Example(){} 
     int doWork() {return 42;} 
}; 
#endif 
// ----------------------------------- 

Template Spezialisierung in einer anderen Datei:

// - templatespecialization.h -------- 
#ifndef TEMPLATESPECIALIZATION_H 
#define TEMPLATESPECIALIZATION_H  
#include "templateexample.h" 
template<> class Example<int> 
{ 
    public: 
     Example() : a(0), b(1), c(2), d(3) {} 
     int doWork() {return a+b+c+d;} 

    private: 
     int a; //<== the specialized object will be larger in memory 
     int b; 
     int c; 
     int d; 
}; 
#endif 
// -------------------------------- 

Haben Sie einen Klasse, die nur die Vorlagenklassendefinition enthält, aber sollte die Spezialisierung enthalten.

// - a.h -------------------------- 
#ifndef A_H 
#define A_H 
#include "templateexample.h" 
class A 
{ 
    public: 
     Example<int> returnSmallExample(); 
}; 
#endif 

// - a.cpp ------------------------ 
#include "a.h" 
Example<int> A::returnSmallExample() {return Example<int>();} 
// -------------------------------- 

Die Hauptklasse jetzt kennt zwei Versionen von Example<int> derjenige von A und das von der templatespecialization.h.

// - main.cpp --------------------- 
#include <iostream> 
#include "a.h" 
#include "templatespecialization.h" 

int main() 
{ 
    A a; 
    Example<int> test = a.returnSmallExample(); 
    std::cout<<test.doWork()<<std::endl; 
} 
// -------------------------------- 

Bitte beachten Sie, dass dieses Problem nur auftreten, wenn separat Klasse A Kompilieren dieses Beispiel aus ideone Ausgängen 6, während unter Verwendung von separaten Dateien in einer Segmentierung Fauls führen kann, oder Ausgang 42 (https://ideone.com/3RTzlC). Auf meinem Rechner erstellt das Beispiel erfolgreich und Ausgänge 2013265920:

proof

In der Serienversion des obigen Beispiel ist alles in eine gemeinsam genutzte Bibliothek, die von Haupt verwendet wird.

Frage 1: Warum erkennt der Linker dieses Problem nicht? Dies sollte leicht zu erkennen sein, indem man die Größe von Objekten vergleicht.

Frage 2: Gibt es eine Möglichkeit, die Objektdateien oder die gemeinsam genutzte Bibliothek zu untersuchen, um mehrere Implementierungen desselben Typs wie im obigen Beispiel zu erkennen?


Bearbeiten: Bitte beachten Sie: Der obige Code ist ein minimales Beispiel, um das Problem zu erklären. Der Grund für die Situation ist, dass die Vorlagenklasse aus einer Bibliothek stammt und ich die Dateien aus dieser Bibliothek nicht bearbeiten kann. Schließlich wird das Ganze überall in der ausführbaren Datei verwendet und jetzt muss ich herausfinden, ob das obige Problem auftritt.


Edit: Code oben kann wie folgt zusammengefasst werden:

#!/bin/bash 
g++ -g -c a.cpp 
g++ -g -c main.cpp 
g++ -o test a.o main.o 
+2

Ihr Code ist grundsätzlich schlecht strukturiert. Jedes Mal, wenn Sie eine Vorlage instanziieren, müssen * alle * Spezialisierungen sichtbar sein. Fügen Sie einige Teilspezialisierungen nicht in eine separate Header-Datei ein. Behalte alles in einer Kopfzeile. –

+0

@KerrekSB dies ist nicht unter meiner Kontrolle. Der Vorlagenkopf stammt aus einer Bibliothek, die ich nicht bearbeiten kann. – Beginner

+7

Sie können jedoch einen eigenen Header schreiben, der die Vorlage enthält und dann die Spezialisierung definiert. Verwenden Sie das im gesamten Programm konsistent und fügen Sie niemals direkt den Header der Bibliothek ein. – Wyzard

Antwort

7

Sie haben unterschiedliche Definitionen derselben Vorlage und ihrer Spezialisierungen in verschiedenen Übersetzungseinheiten. Dies führt zu One Definition Rule Verletzung.

Eine Korrektur wäre, die Spezialisierung in dieselbe Headerdatei zu legen, in der die primäre Klassenvorlage definiert ist.

Frage 1: Warum erkennt der Linker dieses Problem nicht? Dies sollte leicht zu erkennen sein, indem man die Größe von Objekten vergleicht.

Verschiedene Arten können die gleiche Größe haben (z double und int64_t), so offensichtlich nur Größe von Objekten zu vergleichen funktioniert nicht.

Frage 2: Gibt es eine Möglichkeit, die Objektdateien oder die gemeinsam genutzte Bibliothek zu untersuchen, um mehrere Implementierungen desselben Typs wie im obigen Beispiel zu erkennen?

Sie sollten gold linker für die Verknüpfung Ihrer C++ - Anwendungen verwenden, wenn Sie es nicht bereits verwenden. Ein nettes Feature von es ist --detect-odr-violations Befehlszeilenschalter, die genau das tut, was Sie fragen:

Gold verwendet ein heuristisches Potenzial ODR Verletzungen zu finden: wenn das gleiche Symbol definiert gesehen in zwei unterschiedlichen Eingabedateien und die beiden Symbole haben unterschiedliche Größen, dann schaut Gold auf die Debugging-Informationen in den Eingabeobjekten. Wenn die Debugging-Informationen darauf hindeuten, dass die Symbole in verschiedenen Quelldateien definiert wurden, meldet gold eine mögliche ODR-Verletzung. Dieser Ansatz hat sowohl falsche als auch falsche positive Ergebnisse. Es ist jedoch einigermaßen zuverlässig beim Erkennen von Problemen, wenn nicht optimierter Code verknüpft wird. Es ist viel einfacher, diese Probleme bei der Verbindungszeit zu finden, als Fälle mit dem falschen Symbol zu debuggen.

Weitere Details finden Sie unter Enforcing One Definition Rule.

+0

Upvoted, Wirklich coole Funktion, wenn diese existiert! Ich musste meine Antwort ändern :-), denn mit Gold-Linker besteht die Möglichkeit, ODR-Verletzungen zu erfassen. – iammilind

+0

@iammilind Ich benutze diesen Schalter seit 4 Jahren. Einige ODR-Verstöße für mich entdeckt. –

0

Ich weiß nicht, von einer Art und Weise dies der kompilierten binären durch Analyse zu tun, aber man konnte eine Grafik Ihrer bauen Programm #include Beziehungen - es gibt Tools, die dies tun können, wie Doxygen - und verwenden Sie es nach Dateien suchen, die (direkt oder indirekt) die Bibliothek Header, aber nicht die Spezialisierung Header enthalten.

Sie müssten jede Datei untersuchen, um festzustellen, ob sie tatsächlich die fragliche Vorlage verwendet, aber zumindest können Sie den Satz von Dateien eingrenzen, die Sie untersuchen müssen.

+0

Die Sache ist, ich würde gerne davon abkommen, mit includes zu basteln, ohne auch nur zu wissen, ob ich das Problem gelöst habe. Wenn es einen Weg gäbe, die resultierende Bibliothek besser zu verstehen, könnte ich sicher sein, dass ich das Problem genau identifiziert habe. – Beginner

+0

Sie müssen nichts ändern, um das aktuelle Include-Diagramm des Programms zu untersuchen. Und wenn Sie mit Includes so "basteln", dass nichts den Header der Bibliothek enthält, außer über den Header der Spezialisierung, dann wissen Sie, dass Sie das Problem gelöst haben. – Wyzard

+0

Es ist ein guter Vorschlag und ich mache es gerade jetzt. Aber die Anzahl der Includes ist riesig und es gibt viele Konstrukte wie das obige (ich weiß, es ist dumm) und das Programm stürzt sehr selten ab. Ich möchte wirklich die resultierende lib verstehen, um zu sehen, ob das wirklich das Problem ist. – Beginner

1

Frage 1: Warum erkennt der Linker dieses Problem nicht? Dies sollte leicht zu erkennen sein, indem man die Größe von Objekten vergleicht.

Weil dies kein Linker-Problem ist. Im Falle von Vorlagen sollte die Deklaration und alle anderen Spezialisierungen (sei es Klasse oder Funktion) sichtbar Upfront sein.

Frage 2: Gibt es eine Möglichkeit, die Objektdateien oder die gemeinsam genutzte Bibliothek zu untersuchen mehrere Implementierungen des gleichen Typs über wie im Beispiel zu erkennen?

Zumindest ist mir keiner bekannt.

Um diese Situation weiter zu vereinfachen, sehen Sie einen ähnlichen gebrochenen Code:

// foo.h 
inline void foo() { 
#ifdef FOO 
    return; 
#else 
    throw 0; 
#endif 
} 

// foo1.cpp 
#define FOO 
#include"foo.h" 
// ... uses foo() with "return" 

// foo2.cpp 
#include"foo.h" 
// ... uses foo() with "throw" 

Es ist möglich, dass Sie basierend unterschiedliche Ergebnisse erhalten wird, auf dem Weg der Zusammenstellung verwendet.

aktualisieren:
mehrere Körperdefinitionen für eine gleiche Funktion hat, ist undefiniertes Verhalten. Das ist der Grund, warum Sie eine peinliche Ausgabe wie 2013265920 bekommen und das Gleiche passiert auch in meiner Maschine. Der Ausgang sollte entweder oder 6 sein. Ich gab Ihnen oben Beispiel, weil Sie mit inline Funktionen solche Race-Bedingungen erstellen können.

Mit meinem begrenzten Wissen über die Verknüpfungsstufe ist die Verantwortung, die von einem typischen Linker übernommen wird, nur begrenzt, wenn mehr als 1 nichtlineare Funktionsdefinitionen mit der gleichen Signatur abgelehnt werden. z.B.

// Header.h is included in multiple .cpp files 
void foo() {} // rejected due to multiple definitions 
inline void bar() {} // ok because `inline` keyword is found 

Darüber hinaus ist es nicht überprüft, ob die Funktion Körper ähnlich ist oder nicht, denn das ist bereits in früherem Stadium und Linker analysiert wird, nicht die Funktion Körper analysiert.
Nachdem oben gesagt, jetzt die Aufmerksamkeit auf diese Aussage zahlen:

template Funktionen sind immer inline von Natur

Daher Linker keine Chance, sich zu verwerfen bekommen kann.

Der sicherste Weg ist #include der schreibgeschützte Header in Ihrem spezialisierten Header und enthalten, dass spezialisierte Header überall.

+0

@Beginner, aktualisiert. Kurz gesagt, Linker prüft nur, ob eine Funktion "Inline" ist oder nicht, um eine Definition im Header zu erhalten. 'Template'-Funktionen sind immer' inline' und daher immer vom Linker freigegeben. – iammilind

+0

Was ist das für ein Unsinn? –

+0

lesen C++ 11 14.7.3/12 und/oder bieten eine Standard-Zitat für diese "Vorlage Funktionen sind immer inline", die Sie sagen –

0

Ich denke, Sie haben es geschafft, den Compiler zu täuschen. Aber ich sollte beachten, dass Sie meiner Meinung nach die Konzeption von Vorlagen auf eine falsche Art und Weise verstehen, insbesondere versuchen Sie, die Template-Spezialisierung mit der Vererbung zu verwechseln. Ich meine, Template-Spezialisierung darf keine Datenmitglieder zur Klasse hinzufügen, die einzigen Ziele sind Typen für Funktionsparameter und Klassenfelder zu definieren. Wenn Sie Algorithmen ändern, d. H. Code umschreiben oder der Klasse neue Datumselemente hinzufügen möchten, sollten Sie die abgeleitete Klasse definieren. „mehrstufigen“ getrennte Zusammenstellung und Template-Klasse in der Bibliothek bezüglich, C++ die Referenz (http://www.cplusplus.com/doc/oldtutorial/templates/):

Da Vorlagen kompiliert werden, wenn erforderlich ist, dass hierdurch eine Beschränkung Kräfte Mehrdateien Projekte: die Umsetzung (Definition) einer Vorlagenklasse oder -funktion müssen sich in derselben Datei befinden wie ihre Deklaration. Das bedeutet, dass wir die Schnittstelle nicht in einer separaten Header-Datei trennen können und dass sowohl die Schnittstelle als auch die Implementierung in jede Datei eingeschlossen sein müssen, die die Vorlagen verwendet. Da kein Code generiert wird, bis eine Vorlage bei Bedarf instanziiert wird, sind Compiler bereit, die Aufnahme mehrerer Vorlagendateien mit Deklarationen und Definitionen in einem Projekt zuzulassen, ohne dass Verknüpfungsfehler generiert werden.

Verwandte Themen