2009-02-17 12 views
26

Ich muss mit einer Bibliothek umgehen, die aus vielen Vorlagen besteht, die natürlich alle in Header-Dateien implementiert sind. Jetzt versuche ich einen Weg zu finden, die unerträglich langen Kompilierzeiten zu reduzieren, die dadurch entstehen, dass ich die gesamte Bibliothek in jede meiner Kompilationseinheiten aufnehmen muss.Vorlagen: Verwenden Sie Vorwärtsdeklarationen, um die Kompilierzeit zu reduzieren?

Verwenden Forward-Deklarationen eine Möglichkeit, trotz der Vorlagen? Ich versuche etwas in der Richtung des Beispiels unten, wo ich versuchte, um die #include <vector> als Beispiel zu kommen, aber es gibt mir einen Linker-Fehler, weil push_back nicht definiert ist.

#include <iostream> 

namespace std { 
    template<class T> 
    class vector { 
    public: 
    void push_back(const T& t); 
    }; 
} 

int main(int argc, char** argv) { 
    std::vector<int>* vec = new std::vector<int>(); 
    vec->push_back(3); 
    delete vec; 
    return EXIT_SUCCESS; 
} 

$ g++ fwddecl.cpp 
ccuqbCmp.o(.text+0x140): In function `main': 
: undefined reference to `std::vector<int>::push_back(int const&)' 
collect2: ld returned 1 exit status 

Ich habe einmal versucht, vorkompilierte Header aber, dass die Kompilierung mal überhaupt nicht (ich sicher, dass sie in der Tat statt der realen Header geladen wurden machte) ändern. Aber wenn Sie alle sagen, dass vorkompilierte Header der Weg sein sollten, dann versuche ich es noch einmal.

UPDATE: Einige Leute sagen, dass es nicht wert ist, die STL-Klassen weiterzuleiten. Ich sollte betonen, dass die obige STL vector nur ein Beispiel war. Ich versuche nicht wirklich, STL-Klassen weiterzuleiten, aber es geht um andere, stark templatete Klassen einer Bibliothek, die ich verwenden muss.

UPDATE 2: Gibt es eine Möglichkeit, das obige Beispiel richtig zu kompilieren und zu verknüpfen? Logan schlägt vor, -fno-implicit-templates und template class std::vector<int> irgendwo zu verwenden, vermutlich in eine separate Datei, die mit -fno-implicit-templates kompiliert wird, aber ich bekomme immer noch Linker-Fehler. Ich versuche wieder zu verstehen, wie es für std::vector funktioniert, so dass ich es dann auf die Vorlagenklassen anwenden kann, die ich tatsächlich verwende.

+3

in Ihrem Beispiel haben Sie nicht nach vorne nichts erklärt. Sie haben lediglich eine Vorlagenklasse namens vector in namespace std erstellt. Dann haben Sie die von Ihnen angegebene push_back-Methode nicht definiert. Daher der Linkerfehler. –

+0

Second Evans Erklärung, warum Vorwärts-Deklaration std :: vector nicht funktioniert (Sie vermissen mindestens ein Argument in den spitzen Klammern). Verwenden Sie eine von Ihnen selbst geschriebene Vorlagenklasse, von der Sie wissen, dass sie keine Standardvorlagenargumente enthält. –

Antwort

35

Sie können keine "Teile" von Klassen wie dieser weiterleiten. Selbst wenn Sie könnten, müssten Sie den Code irgendwo instanziieren, damit Sie ihn verlinken können. Es gibt Möglichkeiten, damit umzugehen, du könntest eine kleine Bibliothek mit Instanziierungen von gewöhnlichen Containern (z.B. Vektor) machen und sie verknüpfen. Dann müsstest du nur z. Vektor <int> einmal. Um dies zu implementieren, werden Sie so etwas wie -fno-implicit-templates, zumindest verwenden müssen, vorausgesetzt, Sie sind mit g ++ kleben und explizit der Vorlage in Ihrem lib mit template class std::vector<int>


So instanziiert, ein echtes Arbeitsbeispiel. Hier habe ich 2 Dateien, a.cpp und b.cpp

a.cpp:

#include <vector> // still need to know the interface 
#include <cstdlib> 

int main(int argc, char **argv) { 
    std::vector<int>* vec = new std::vector<int>(); 
    vec->push_back(3); 
    delete vec; 
    return EXIT_SUCCESS; 
} 

So, jetzt kann ich kompilieren a.cpp mit -fno-implicit-templates:

g++ -fno-implicit-templates -c a.cpp 

Das wird mir geben ao Wenn ich dann versuche, eine Verbindung herzustellen, bekomme ich:

g++ a.o 
/usr/bin/ld: Undefined symbols: 
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&) 
void std::_Destroy<int*, std::allocator<int> >(int*, int*, std::allocator<int>) 
collect2: ld returned 1 exit status 

Nicht gut. Also wenden wir uns an b.cpp:

#include <vector> 
template class std::vector<int>; 
template void std::_Destroy(int*,int*, std::allocator<int>); 
template void std::__uninitialized_fill_n_a(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, unsigned long, int const&, std::allocator<int>); 
template void std::__uninitialized_fill_n_a(int*, unsigned long, int const&, std::allocator<int>); 
template void std::fill(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&); 
template __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > std::fill_n(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, unsigned long, int const&); 
template int* std::fill_n(int*, unsigned long, int const&); 
template void std::_Destroy(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, std::allocator<int>); 

Jetzt sagen Sie zu sich selbst, woher kamen alle diese zusätzlichen Vorlagen-Sachen? Ich sehe die template class std::vector<int> und das ist in Ordnung, aber was ist mit dem Rest davon?Nun, die kurze Antwort ist, dass diese Dinge Implementierungen notwendigerweise ein wenig unordentlich sind, und wenn Sie sie manuell instanziieren, durch Erweiterung einige der Unordnung austritt. Sie fragen sich wahrscheinlich, wie ich überhaupt herausgefunden habe, was ich instanziieren musste. Nun, ich habe die Linker-Fehler verwendet;).

So, jetzt stellen wir b.cpp

g++ -fno-implicit-templates -c b.cpp 

Und wir bekommen B. O. Verknüpfen a.o und b.o können wir

g++ a.o b.o 

Hurra, keine Linker-Fehler.

Also, um in einige Details über Ihre aktualisierte Frage zu kommen, wenn es eine selbst gebraute Klasse ist, muss es nicht unbedingt so unordentlich sein. Zum Beispiel können Sie die Schnittstelle von der Implementierung trennen, z. sagen wir ch, c.cpp, zusätzlich zu a.cpp und b.cpp

ch

template<typename T> 
class MyExample { 
    T m_t; 
    MyExample(const T& t); 
    T get(); 
    void set(const T& t); 
}; 

c.cpp

template<typename T> 
MyExample<T>::MyExample(const T& t) : m_t(t) {} 
template<typename T> 
T MyExample<T>::get() { return m_t; } 
template<typename T> 
void MyExample<T>::set(const T& t) { m_t = t; } 

a.cpp

#include "c.h" // only need interface 
#include <iostream> 
int main() { 
    MyExample<int> x(10); 
    std::cout << x.get() << std::endl; 
    x.set(9); 
    std::cout << x.get() << std::endl; 
    return EXIT_SUCCESS; 
} 
haben

b.cpp, die "Bibliothek":

#include "c.h" // need interface 
#include "c.cpp" // need implementation to actually instantiate it 
template class MyExample<int>; 

Jetzt kompilieren Sie b.cpp einmal b.o. Wenn sich a.cpp ändert, müssen Sie nur das neu kompilieren und in b.o.

+0

Ausgezeichnete Antwort! Danke für den Schritt-für-Schritt-Spaziergang durch :) –

+0

+1 Lange Vorlage Bauzeiten treiben mich verrückt! tyvm für das detaillierte Beispiel, wie man aus der Vorlage-Build-Hölle ... – kfmfe04

3

Es gibt <iosfwd>, die Ihnen einige Forward-Deklaration für die Iostream-Klassen geben, aber im Allgemeinen gibt es nicht viel, was Sie tun können, über die STL-Vorlagen in Bezug auf Vorwärts deklarieren sie.

Vorkompilierte Header sind der richtige Weg. Sie werden keine Geschwindigkeitszunahme feststellen, wenn Sie sie das erste Mal kompilieren, aber Sie sollten diesen Preis nur einmal für jedes Mal zahlen, wenn Sie den vorkompilierten Header (oder alles, was darin enthalten ist) ändern.

See this question für andere Ideen zur Beschleunigung der Kompilierung.

6

Mit Forward-Deklarationen können Sie nur Member oder Parameter als Zeiger oder Verweis auf diesen Typ deklarieren. Sie können keine Methoden oder andere Dinge verwenden, die die Innereien dieses Typs erfordern. Das sagte ich fand Vorwärtsdeklarationen wirklich begrenzend, wenn man versucht, Kompilierungszeiten zu beschleunigen. Ich schlage vor, Sie untersuchen die Möglichkeit von vorkompilierten Headern ein wenig mehr, da ich fand, dass sie wirklich mit Kompilierungszeiten helfen, obwohl das mit Visual C++ unter Windows und nicht g ++ war.

+0

Wenn Sie Cmake verwenden, ist das Cotire-Plugin erstaunlich für die Pflege von PCH's für Sie. https://github.com/sakra/cotire Es ist super einfach zu bedienen und "funktioniert einfach" – xaxxon

22

Vorwärtsdeklarationen können Sie dies tun:

template <class T> class vector; 

Dann Sie Verweise auf und Zeiger auf vector<whatever> ohne Vektor zu definieren (ohne einschließlich vector ‚s-Header-Datei) erklären kann. Dies funktioniert genauso wie Vorwärtsdeklarationen regulärer (Nicht-Template-) Klassen.

Das Problem mit Vorlagen ist insbesondere, dass Sie normalerweise nicht nur die Klassendeklaration, sondern auch alle Methodendefinitionen in Ihrer Header-Datei benötigen (damit der Compiler die benötigten Vorlagen instanziieren kann).Explizite Template Instanziierung (die Sie die Verwendung von mit -fno-implicit-templates erzwingen können) ist eine Problemumgehung dafür; Sie können Ihre Methodendefinitionen in einer Quelldatei (oder nach dem Beispiel der Google Style Guide, in einer -inl.h Header-Datei, die Sie nicht einschließen müssen) dann explizit instanziiert sie so:

template <class int> class vector; 

Beachten Sie, dass Sie brauchen nicht wirklich -fno-implicit-templates, um davon zu profitieren; Der Compiler wird stillschweigend vermeiden, alle Vorlagen zu instanziieren, für die er keine Definitionen hat, vorausgesetzt, dass der Linker es später herausfinden wird. Und das Hinzufügen -fno-implicit-templates macht alle Vorlagen härter (nicht nur die zeitaufwendigen), so würde ich es nicht empfehlen.

Das Problem mit Ihrem Beispielcode ist, dass Sie nicht die echte std::vector Klasse weiterleiten. Wenn Sie <vector> nicht verwenden, erstellen Sie Ihre eigene, nicht standardmäßige Klasse vector, und Sie definieren nie push_back, sodass der Compiler nichts instanziieren kann.

Ich habe vorkompilierte Header mit großer Wirkung verwendet; Ich bin mir nicht sicher, warum sie dir nicht geholfen haben. Sie setzen alle Ihre unveränderlichen Header in einem einzigen all.h, vorkompiliert und verifiziert mit strace oder ähnlich, dass all.h.pch geladen wurde und einzelne Header-Dateien nicht waren? (Wie strace zu verwenden:. Statt g++ mytest.cc läuft, laufen strace -o strace.out g++ mytest.cc, dann strace.out in einem Texteditor anzeigen und für open( Anrufe suchen, um zu sehen, welche Dateien gelesen werden)

+2

Sie bringen einen guten Punkt wrt-fno-implizite-Vorlagen. Ich war irgendwie abgelenkt von der Idee, Vorlagen "nach vorne zu deklarieren", wenn das Problem die Übersetzungsgeschwindigkeit ist. –