c++ - Plantillas: ¿usar declaraciones directas para reducir el tiempo de compilación?
templates linker (4)
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 ++.
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.
¿Es posible usar declaraciones directas, a pesar de las plantillas? Estoy intentando algo en la línea del ejemplo a continuación, donde intenté #include <vector>
el #include <vector>
, por ejemplo, pero me está dando un error de 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
Probé los encabezados precompilados una vez, pero eso no cambió en absoluto los tiempos de compilación (me aseguré de que estuvieran realmente cargados 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 enfatizar que el vector
STL de arriba fue 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>
algún lugar, presumiblemente en un archivo .cpp
separado que se compila con -fno-implicit-templates
, pero aún así recibo 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.
Hay <iosfwd>
que le dará alguna declaración de avance para las clases de iostream, pero en general no hay mucho que pueda hacer con respecto a las plantillas de stl en términos de declararlas 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).
Vea esta pregunta para otras ideas sobre cómo acelerar la compilación.
Las declaraciones adelante le permiten hacer esto:
template <class T> class vector;
Luego puede declarar referencias y punteros a vector<whatever>
sin definir vector (sin incluir el archivo de cabecera del vector
). 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; puede poner sus definiciones de métodos en un archivo fuente (o, siguiendo el ejemplo de la Guía de estilo de Google , en un archivo de encabezado -inl.h
que no tiene que incluir) y luego crear instancias explícitas 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 está adelante declarando la verdadera clase std::vector
. Al no incluir <vector>
, está creando su propia clase de vector
no estándar, y nunca push_back
, por lo que el compilador no push_back
que crear instancias.
He usado encabezados precompilados con gran efecto; No estoy seguro de por qué no te ayudaron. ¿ all.h
todos tus encabezados no cambiantes 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 usar strace
: en lugar de ejecutar g++ mytest.cc
, ejecute strace -o strace.out g++ mytest.cc
, luego vea strace.out
en un editor de texto y busque open(
llamadas para ver qué archivos se están leyendo).
No puede reenviar declarar "partes" de clases así. 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ía que compilar, por ejemplo, vector <int> una vez. Para implementar esto, necesitarás usar algo como -fno-implicit-templates
, al menos, suponiendo que te apegas a g ++ y crear instancias explícitas de la plantilla en tu lib con template class std::vector<int>
Entonces, 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 un.cpp con -fno-implicit-templates
:
g++ -fno-implicit-templates -c a.cpp
Esto me dará un ao. Si luego trato de vincular, obtengo:
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. Entonces volvemos 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 te estás diciendo a ti mismo, ¿de dónde vienen todas estas plantillas extra? Veo la template class std::vector<int>
y está bien, pero ¿qué hay del 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;).
Entonces ahora compilamos b.cpp
g++ -fno-implicit-templates -c b.cpp
Y obtenemos bo Linking ao y bo podemos obtener
g++ a.o b.o
Hooray, no hay errores de enlazador.
Entonces, 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, por ejemplo, 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 a bo una vez. Cuando a.cpp cambia, solo necesita recompilarlo y vincularlo en bo