2009-02-17 13 views
26

Tengo que lidiar con una biblioteca que consta de muchas clases con plantillas, que por supuesto están implementadas en archivos de encabezado. Ahora estoy tratando de encontrar una manera de reducir los tiempos de compilación insoportablemente largos que provienen del hecho de que prácticamente tengo que incluir toda la biblioteca en cada una de mis unidades de compilación.Plantillas: ¿Utilizar declaraciones directas para reducir el tiempo de compilación?

¿Se puede usar una declaración anticipada, a pesar de las plantillas? Estoy intentando algo en la línea del ejemplo a continuación, donde intenté dar la vuelta al #include <vector>, como ejemplo, pero me está dando un error del enlazador porque push_back no está definido.

#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 

me trataron encabezados precompilados una vez, pero eso no cambia los tiempos de compilación en absoluto (que se aseguró de que estaban cargados de hecho en lugar de los encabezados reales). Pero si todos dicen que los encabezados precompilados deberían ser el camino a seguir, lo volveré a intentar.

ACTUALIZACIÓN: Algunas personas dicen que no vale la pena reenviar-declarar las clases de STL. Debo recalcar que el STL vector anterior era solo un ejemplo. Realmente no estoy tratando de reenviar-declarar las clases de STL, pero se trata de otras clases de una biblioteca con muchas plantillas que tengo que usar.

ACTUALIZACIÓN 2: ¿Hay alguna manera de hacer que el ejemplo anterior realmente compile y enlace correctamente? Logan sugiere usar -fno-implicit-templates y poner template class std::vector<int> en algún lugar, presumiblemente en un archivo separado .cpp que se compila con -fno-implicit-templates, pero sigo recibiendo errores del enlazador. Una vez más, estoy tratando de entender cómo funciona para std::vector para poder aplicarlo a las clases con plantillas que estoy usando.

+3

en su ejemplo, no se ha declarado hacia adelante nada. Todo lo que hizo fue crear una clase de plantilla llamada vector en el espacio de nombres std. Luego no definió el método push_back que declaró en él. De ahí el error del enlazador. –

+0

Segunda explicación de Evan sobre por qué el std :: vector de declaración hacia adelante no está funcionando (le falta al menos un argumento dentro de los corchetes angulares). Intenta usar una clase de plantilla que escribiste tú mismo, que sabes que no tiene argumentos de plantilla predeterminados. –

Respuesta

35

No puede reenviar declarar "partes" de clases como esa. Incluso si pudieras, aún necesitarías crear una instancia del código en alguna parte para poder enlazarlo. Hay formas de manejarlo, puede hacerse una pequeña biblioteca con instancias de contenedores comunes (por ejemplo, vector) y vincularlos. Entonces, solo tendrá que compilar, por ejemplo, vector <int> una vez. Para implementar esta tendrá que usar algo como -fno-implicit-templates, al menos, suponiendo que se están pegando con g ++ y explícitamente una instancia de la plantilla en el lib con template class std::vector<int>


Por lo tanto, un verdadero ejemplo de trabajo. Aquí tengo 2 archivos, a.cpp y 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; 
} 

Así que ahora puedo compilar a.cpp con -fno-implicit-templates:

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

Esto me dará ao Si yo entonces trato de vincular a.o consigo:

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 

No es bueno. Así que nos dirigimos a 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>); 

Ahora que estás diciendo a ti mismo, ¿de dónde todas estas cosas plantilla adicionales vienen? Veo el template class std::vector<int> y está bien, pero ¿y el resto?Bueno, la respuesta corta es que estas implementaciones son, por necesidad, un poco desordenadas, y cuando las instancias manualmente, por extensión, parte de este desorden se filtra. Probablemente te estés preguntando cómo descubrí lo que necesitaba para crear una instancia. Bueno, utilicé los errores del enlazador;).

Así que ahora compilar b.cpp

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

y tenemos B.O. Vinculación de a.o y b.o podemos obtener

g++ a.o b.o 

Hooray, no hay errores de enlazador.

Por lo tanto, para entrar en detalles sobre su pregunta actualizada, si esta es una clase elaborada en casa no necesariamente tiene que ser tan desordenada. Por ejemplo, puede separar la interfaz de la implementación, p. decir que tenemos ch, c.cpp, además de a.cpp y 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; 
} 

b.cpp, la "biblioteca":

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

Ahora compila b.cpp to b.o una vez. Cuando a.cpp cambia, solo necesita recompilarlo y vincularlo en b.o.

+0

¡Excelente respuesta! Gracias por el recorrido paso a paso :) –

+0

+1 ¡Los tiempos de compilación largos de la plantilla me están volviendo loco! tyvm para el ejemplo detallado de cómo salir de template-build-hell ... – kfmfe04

3

Hay <iosfwd> que le dará alguna declaración hacia adelante para las clases iostream, pero en general no hay mucho que pueda hacer con respecto a las plantillas stl en términos de anunciarlas hacia adelante.

Los encabezados pre compilados son el camino a seguir. No notará ningún aumento de velocidad la primera vez que los compila, pero solo debe pagar ese precio una vez por cada vez que modifique el encabezado precompilado (o cualquier elemento incluido en él).

See this question para otras ideas sobre la aceleración de la compilación.

6

Con las declaraciones avanzadas, solo puede declarar miembros o parámetros como puntero o referencia a ese tipo. No puede usar ningún método u otras cosas que requieran las entrañas de dicho tipo. Dicho esto, encontré declaraciones directas que realmente limitaban la velocidad de compilación. Sugiero que investigue un poco más la posibilidad de encabezados precompilados ya que los encontré que realmente ayudan con los tiempos de compilación, aunque eso fue con el uso de Visual C++ en Windows y no en g ++.

+0

Si está usando cmake, el complemento de cotire es increíble para el cuidado de PCH para usted. https://github.com/sakra/cotire Es súper fácil de usar y "simplemente funciona" – xaxxon

22

declaraciones adelantadas permiten hacer esto:

template <class T> class vector; 

A continuación, se puede declarar referencias y punteros a vector<whatever> sin definir vector (sin incluir el archivo de cabecera vector 's). Esto funciona igual que las declaraciones directas de clases regulares (sin plantilla). El problema con las plantillas en particular es que generalmente necesita no solo la declaración de clase sino también todas las definiciones de métodos en su archivo de encabezado (para que el compilador pueda instanciar las plantillas necesarias).La creación de instancias de plantilla explícita (que puede forzar el uso de -fno-implicit-templates) es una solución para esto; usted puede poner sus definiciones de métodos en un archivo de origen (o, siguiendo el ejemplo de la Google Style Guide, en un archivo -inl.h cabecera que usted no tiene que incluir) a continuación de forma explícita una instancia de ellos de esta manera:

template <class int> class vector; 

Tenga en cuenta que en realidad no necesita -fno-implicit-templates para beneficiarse de esto; el compilador evitará instantáneamente la creación de instancias de cualquier plantilla para la que no tenga definiciones, bajo el supuesto de que el enlazador la resolverá más adelante. Y agregar -fno-implicit-templates hará que usar todas las plantillas sea más difícil (no solo las que consumen mucho tiempo), así que no lo recomendaría.

El problema con su código de ejemplo es que no avanza declarando la verdadera clase std::vector. Al no incluir <vector>, está creando su propia clase vector no estándar, y no siempre está definiendo push_back, por lo que no hay nada para que el compilador cree una instancia.

He usado encabezados precompilados con gran efecto; No estoy seguro de por qué no te ayudaron. Pusiste todos los encabezados que no cambian en un solo all.h, lo precompiló y verificó con strace o similar que all.h.pch se cargó y los archivos de encabezado individuales no? (Cómo utilizar strace:. g++ mytest.cc en lugar de correr, correr strace -o strace.out g++ mytest.cc, a continuación, ver strace.out en un editor de texto y la búsqueda de open( llamadas para ver qué archivos están siendo leídos)

+2

Presenta un buen punto wrt -fno-implicit-templates. Me distraje por la idea de las plantillas de "declaración hacia adelante", cuando el problema es la velocidad de compilación. –

Cuestiones relacionadas