views:

2269

answers:

4

I have to deal with a library that consists of many templated classes, which are of course all implemented in header files. Now I'm trying to find a way to reduce the unbearably long compile times that come from the fact that I pretty much have to include the whole library in each and one of my compilation units.

Is using forward declarations a possibility, despite the templates? I'm trying something along the lines of the example below, where I attempted to get around the #include <vector>, as an example, but it's giving me a linker error because push_back is undefined.

#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

I tried precompiled headers once but that didn't change the compile times at all (I did make sure they were indeed loaded instead of the real headers). But if you all say that precompiled headers should be the way to go then I'll give that a try again.

UPDATE: Some people say it's not worth to forward-declare the STL classes. I should stress that the STL vector above was just an example. I'm not really trying to forward-declare STL classes, but it's about other, heavily templated classes of some library that I have to use.

UPDATE 2: Is there a way to make above example actually compile and link properly? Logan suggests to use -fno-implicit-templates and put template class std::vector<int> somewhere, presumably into a separate .cpp file that gets compiled with -fno-implicit-templates, but I still get linker errors. Again, I'm trying to understand how it works for std::vector so that I can then apply it to the templated classes that I'm actually using.

+17  A: 

You can't forward declare "parts" of classes like that. Even if you could, you'd still need to instantiate the code somewhere so you could link against it. There are ways to handle it, you could make yourself a little library with instantiations of common containers (e.g. vector) and link them in. Then you'd only ever need to compile e.g. vector<int> once. To implement this you'll need to use something like -fno-implicit-templates, at least assuming you are sticking with g++ and explicitly instantiate the template in your lib with template class std::vector<int>


So, a real working example. Here I have 2 files, a.cpp and 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 now I can compile a.cpp with -fno-implicit-templates:

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

This will give me a.o. If I then I try to link a.o I get:

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 good. So we turn to 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>);

Now you're saying to yourself, where did all these extra template things come from? I see the template class std::vector<int> and that's fine, but what about the rest of it? Well the short answer is that, these things implementations are by necessity a little messy, and when you manually instantiate them, by extension some of this messiness leaks out. You're probably wondering how I even figured out what I needed to instantiate. Well I used the linker errors ;).

So now we compile b.cpp

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

And we get b.o. Linking a.o and b.o we can get

g++ a.o b.o

Hooray, no linker errors.

So, to get into some details about your updated question, if this is a home brewed class it doesn't necessarily have to be this messy. For instance, you can separate the interface from the implementation, e.g. say we have c.h, c.cpp, in addition to a.cpp and b.cpp

c.h

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, the "library":

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

Now you compile b.cpp to b.o once. When a.cpp changes you just need to recompile that and link in b.o.

Logan Capaldo
+2  A: 

There is <iosfwd> that will give you some forward declaration for the iostream classes, but in general there's not much you can do about the stl templates in terms of forward declaring them.

Pre-compiled headers are the way to go. You won't notice any speed-increase the first time you compile them, but you should only pay that price once for every time you modify the precompiled header (or anything included in it).

See this question for other ideas about speeding up compilation.

Eclipse
+4  A: 

With forward declarations you can only declare members or parameters as pointer or reference to that type. You cannot use any methods or other things that require the innards of said type. That said I found forward declarations really limiting when trying to speed up compilation times. I suggest you investigate the possibility of precompiled headers a bit more since I found them to really help with compilation times, though that was with using Visual C++ on Windows and not g++.

gix
+8  A: 

Forward declarations let you do this:

template <class T> class vector;

Then you can declare references to and pointers to vector<whatever> without defining vector (without including vector's header file). This works the same as forward declarations of regular (non-template) classes.

The problem with templates in particular is that you usually need not just the class declaration but also all of the method definitions in your header file (so that the compiler can instantiate the needed templates). Explicit template instantiation (which you can force the use of with -fno-implicit-templates) is a workaround for this; you can put your method definitions in a source file (or, following the example of the Google Style Guide, in a -inl.h header file which you don't have to include) then explicitly instantiate them like this:

template <class int> class vector;

Note that you don't actually need -fno-implicit-templates to benefit from this; the compiler will silently avoid instantiating any templates it has no definitions for, on the assumption that the linker will figure it out later. And adding -fno-implicit-templates will make using all templates harder (not just the time consuming ones), so I wouldn't recommend it.

The problem with your example code is that you're not forward declaring the true std::vector class. By not including <vector>, you're creating your own, nonstandard vector class, and you're not ever defining push_back, so there's nothing for the compiler to instantiate.

I've used precompiled headers to great effect; I'm not sure why they didn't help you. You put all of your non-changing headers in a single all.h, precompiled it, and verified with strace or similar that all.h.pch was loaded and individual header files were not? (How to use strace: instead of running g++ mytest.cc, run strace -o strace.out g++ mytest.cc, then view strace.out in a text editor and search for open( calls to see which files are being read.)

Josh Kelley
You bring up a good point wrt -fno-implicit-templates. I got sort of distracted by the idea of "forward declaring" templates, when the problem is compilation speed.
Logan Capaldo