Шаблоны: Использовать предварительные объявления, чтобы сократить время компиляции? - PullRequest
26 голосов
/ 17 февраля 2009

Мне приходится иметь дело с библиотекой, которая состоит из множества шаблонных классов, которые, конечно, все реализованы в заголовочных файлах. Теперь я пытаюсь найти способ сократить невыносимо длительное время компиляции, которое объясняется тем, что мне приходится включать целую библиотеку в каждый из моих модулей компиляции.

Возможно ли использование предварительных объявлений, несмотря на шаблоны? Я пытаюсь что-то в соответствии с примером ниже, где я попытался обойти #include <vector>, в качестве примера, но это дает мне ошибку компоновщика, потому что push_back не определено.

#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

Я пробовал предварительно скомпилированные заголовки, но это никак не изменило время компиляции (я действительно удостоверился, что они действительно загружены вместо реальных заголовков). Но если вы все скажете, что предварительно скомпилированные заголовки должны быть использованы, я попробую еще раз.

ОБНОВЛЕНИЕ: Некоторые люди говорят, что не стоит заранее объявлять классы STL. Я должен подчеркнуть, что STL vector выше был только примером. На самом деле я не пытаюсь заранее объявить классы STL, но речь идет о других, сильно шаблонизированных классах некоторой библиотеки, которые я должен использовать.

ОБНОВЛЕНИЕ 2: Есть ли способ, чтобы приведенный выше пример действительно компилировался и правильно связывался? Логан предлагает использовать -fno-implicit-templates и поместить куда-нибудь template class std::vector<int>, предположительно в отдельный файл .cpp, который компилируется с -fno-implicit-templates, но я все еще получаю ошибки компоновщика. Опять же, я пытаюсь понять, как это работает для std::vector, чтобы затем применить его к шаблонным классам, которые я на самом деле использую.

Ответы [ 4 ]

35 голосов
/ 17 февраля 2009

Вы не можете пересылать объявления "частей" таких классов. Даже если бы вы могли, вам все равно нужно было бы где-то создать экземпляр кода, чтобы вы могли ссылаться на него. Есть способы справиться с этим, вы можете создать небольшую библиотеку с экземплярами общих контейнеров (например, vector) и связать их. Тогда вам нужно будет только скомпилировать, например. вектор один раз. Чтобы реализовать это, вам нужно будет использовать что-то вроде -fno-implicit-templates, по крайней мере, предполагая, что вы придерживаетесь g ++ и явно создаете экземпляр шаблона в вашей библиотеке с помощью template class std::vector<int>


Итак, настоящий рабочий пример. Здесь у меня есть 2 файла, a.cpp и 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;
}

Так что теперь я могу скомпилировать a.cpp с -fno-implicit-templates:

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

Это даст мне о.о. Если я тогда пытаюсь связать, я получаю:

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

Ничего хорошего. Итак, мы переходим к 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>);

Теперь вы говорите себе, откуда взялись все эти дополнительные шаблоны? Я вижу template class std::vector<int>, и это нормально, но как насчет остального? Короче говоря, короткий ответ заключается в том, что эти вещи по необходимости немного запутаны, и когда вы создаете их вручную, из-за этого некоторые из этих запутанностей просачиваются. Вы, наверное, удивляетесь, как я понял, что мне нужно для создания экземпляра. Ну я использовал ошибки компоновщика;).

Итак, теперь мы компилируем b.cpp

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

И мы получаем Связав а.о и б.о мы можем получить

g++ a.o b.o

Ура, нет ошибок компоновщика.

Итак, чтобы получить некоторые подробности о вашем обновленном вопросе, если это класс домашнего приготовления, он не обязательно должен быть таким грязным. Например, вы можете отделить интерфейс от реализации, например, скажем, у нас есть c.h, c.cpp, в дополнение к a.cpp и 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, «библиотека»:

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

Теперь вы компилируете b.cpp в bo один раз. Когда a.cpp изменится, вам просто нужно перекомпилировать это и связать в b.o.

22 голосов
/ 17 февраля 2009

Предварительные декларации позволяют вам сделать это:

template <class T> class vector;

Затем вы можете объявить ссылки и указатели на vector<whatever> без определения вектора (без включения заголовочного файла vector). Это работает так же, как и предварительные объявления обычных (не шаблонных) классов.

Проблема с шаблонами, в частности, заключается в том, что вам обычно требуется не только объявление класса, но и все определения методов в вашем заголовочном файле (чтобы компилятор мог создавать экземпляры необходимых шаблонов). Явное создание шаблона (которое можно принудительно использовать с помощью -fno-implicit-templates) - это обходной путь; вы можете поместить определения вашего метода в исходный файл (или, следуя примеру Google Style Guide , в заголовочный файл -inl.h, который вам не нужно включать), а затем явно создать их экземпляры следующим образом :

template <class int> class vector;

Обратите внимание, что вам на самом деле не нужно -fno-implicit-templates, чтобы извлечь из этого пользу; компилятор будет молча избегать создания экземпляров любых шаблонов, для которых у него нет определений, при условии, что компоновщик выяснит это позже. А добавление -fno-implicit-templates сделает использование всех шаблонов сложнее (не только трудоемких), поэтому я бы не рекомендовал это.

Проблема с вашим примером кода состоит в том, что вы не ожидаете объявить истинный класс std::vector. Не включая <vector>, вы создаете свой собственный нестандартный класс vector и никогда не определяете push_back, поэтому компилятору нечего создавать.

Я использовал предварительно скомпилированные заголовки для отличного эффекта; Я не уверен, почему они не помогли тебе. Вы поместили все ваши неизменяемые заголовки в один all.h, предварительно скомпилировали его и проверили с помощью strace или аналогичным образом, что all.h.pch был загружен, а отдельные заголовочные файлы не были? (Как использовать strace: вместо g++ mytest.cc, запустите strace -o strace.out g++ mytest.cc, затем просмотрите strace.out в текстовом редакторе и выполните поиск open( вызовов, чтобы увидеть, какие файлы читаются.)

6 голосов
/ 17 февраля 2009

С помощью предварительных объявлений вы можете объявлять только элементы или параметры как указатели или ссылки на этот тип. Вы не можете использовать какие-либо методы или другие вещи, которые требуют внутренностей указанного типа. Тем не менее, я обнаружил, что предварительные объявления действительно ограничены при попытке ускорить время компиляции. Я предлагаю вам исследовать возможность предварительно скомпилированных заголовков, так как я обнаружил, что они действительно помогают со временем компиляции, хотя это было с использованием Visual C ++ в Windows, а не g ++.

3 голосов
/ 17 февраля 2009

Существует <iosfwd>, который даст вам некоторое предварительное объявление для классов iostream, но, в общем, вы мало что можете сделать с шаблонами stl с точки зрения их прямого объявления.

Предварительно скомпилированные заголовки - путь. Вы не заметите никакого увеличения скорости при первой компиляции, но вы должны платить эту цену только один раз за каждый раз, когда вы изменяете предварительно скомпилированный заголовок (или что-либо включенное в него).

См. Этот вопрос для других идей по ускорению компиляции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...