2009-02-17 1 views
26

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

Является ли использование форвардных объявлений возможностью, несмотря на шаблоны? Я пытаюсь что-то сделать в соответствии с приведенным ниже примером, где я попытался обойти #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, но это касается других, сильно шаблонных классов некоторой библиотеки, которые я должен использовать.

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

+3

в вашем примере, вы не вперед объявили ничего. Все, что вы сделали, это создать шаблонный шаблон с именем vector в пространстве имен std. Тогда вам не удалось определить метод push_back, который вы указали в нем. Следовательно, ошибка компоновщика. –

+0

Второе объяснение Эвана о том, почему forward decl объявление std :: vector не работает (вам не хватает хотя бы одного аргумента внутри угловых скобок). Попробуйте использовать класс шаблонов, который вы написали сами, что, как вы знаете, не имеет аргументов шаблона по умолчанию. –

ответ

35

Вы не можете переслать декларацию «частей» классов. Даже если бы вы могли, вам все равно нужно было бы создать код где-нибудь, чтобы вы могли ссылаться на него. Есть способы справиться с этим, вы можете сделать себе небольшую библиотеку с экземплярами общих контейнеров (например, вектор) и связать их. Тогда вам нужно будет когда-либо компилировать, например. вектор <int> один раз. Для реализации этого вам нужно использовать что-то вроде -fno-implicit-templates, по крайней мере, если вы наклеивание с г ++ и явно экземпляр шаблона в вашей библиотеке, с 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 

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

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 

И мы получаем в обратном порядке Связывание a.o и b.o мы можем получить

g++ a.o b.o 

Hooray, отсутствие ошибок компоновщика.

Итак, чтобы получить некоторые детали о вашем обновленном вопросе, если это домашний класс, он не обязательно должен быть таким грязным. Например, вы можете отделить интерфейс от реализации, например. что у нас есть сп, c.cpp, в дополнение к a.cpp и b.cpp

ч

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, «library»:

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

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

+0

Отличный ответ! Спасибо за пошаговую прогулку :) :) –

+0

+1 Длинное время сборки шаблона меня заводит! tyvm для подробного примера того, как выйти из шаблона-построения-ада ... – kfmfe04

3

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

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

See this question для других идей об ускорении компиляции.

6

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

+0

Если вы используете cmake, плагин cotire поражает заботой о PCH для вас. https://github.com/sakra/cotire Это очень просто в использовании и «просто работает» – xaxxon

22

декларации Вперед позволяют это сделать:

template <class T> class vector; 

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

Проблема с шаблонами в частности заключается в том, что вам обычно требуется не только объявление класса, но и все определения методов в вашем файле заголовка (чтобы компилятор мог создавать экземпляры необходимых шаблонов).Явный шаблонный экземпляр (который вы можете принудительно использовать с помощью -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( вызовов, чтобы увидеть, какие файлы читаются)

+2

Вы поднимаете хорошую точку wrt -fno-implicit-templates. Я отвлекся от идеи «форвардного объявления» шаблонов, когда проблема - это скорость компиляции. –

Смежные вопросы