Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templates: Use forward declarations to reduce compile time?

People also ask

Are templates resolved at compile-time?

All the template parameters are fixed+known at compile-time. If there are compiler errors due to template instantiation, they must be caught at compile-time!

Why should I use forward declarations?

A forward declaration allows us to tell the compiler about the existence of an identifier before actually defining the identifier. In the case of functions, this allows us to tell the compiler about the existence of a function before we define the function's body.

What is the purpose of forward declaration of a class?

A forward declaration tells the compiler about the existence of an entity before actually defining the entity. Forward declarations can also be used with other entity in C++, such as functions, variables and user-defined types.

Are templates instantiated at compile-time?

7.2. Instantiation is the process by which a C++ compiler creates a usable function or object from a template. The C++ compiler uses compile-time instantiation, which forces instantiations to occur when the reference to the template is being compiled.


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.


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.)


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++.


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.