简体   繁体   English

模板:使用前向声明来减少编译时间?

[英]Templates: Use forward declarations to reduce compile time?

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

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. 更新:有些人说转发申报STL课程是不值得的。 I should stress that the STL vector above was just an example. 我应该强调上面的STL vector只是一个例子。 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. 我并没有真正尝试向前声明STL类,但它是关于我必须使用的某些库的其他严格模板化的类。

UPDATE 2: Is there a way to make above example actually compile and link properly? 更新2:有没有办法使上面的例子实际编译和链接正确? 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. Logan建议使用-fno-implicit-templates并将template class std::vector<int>放在某处,大概是一个单独的.cpp文件,它用-fno-implicit-templates编译,但我仍然会遇到链接器错误。 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. 再一次,我试图理解它如何用于std::vector以便我可以将它应用于我实际使用的模板化类。

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 (eg vector) and link them in. Then you'd only ever need to compile eg vector<int> once. 有办法处理它,你可以让自己成为一个带有常见容器实例的小库(例如vector)并将它们链接起来。然后你只需要编译例如vector <int>一次。 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> 要实现这一点,你需要使用-fno-implicit-templates类的东西,至少假设你坚持使用g ++并使用template class std::vector<int>在lib中显式实例化模板


So, a real working example. 所以,一个真实的例子。 Here I have 2 files, a.cpp and b.cpp 这里我有2个文件,a.cp​​p和b.cpp

a.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 : 所以现在我可以使用-fno-implicit-templates编译a.cpp:

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

This will give me ao If I then I try to link ao 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: 所以我们转向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? 我看到template class std::vector<int> ,这很好,但其余的呢? 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 所以现在我们编译b.cpp

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

And we get bo Linking ao and bo we can get 我们得到了联系我们和我们可以得到的bo

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, eg say we have ch, c.cpp, in addition to a.cpp and b.cpp 例如,您可以将接口与实现分开,例如,除了a.cpp和b.cpp之外,我们还有ch,c.cpp。

ch CH

template<typename T>
class MyExample {
  T m_t;
  MyExample(const T& t);
  T get();
  void set(const T& t);
};

c.cpp 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 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": b.cpp,“图书馆”:

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

Now you compile b.cpp to bo once. 现在你将b.cpp编译为bo一次。 When a.cpp changes you just need to recompile that and link in bo 当a.cpp改变时,你只需要重新编译它并在bo中链接

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). 然后你可以声明对vector<whatever>引用和指针,而无需定义vector(不包含vector的头文件)。 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; 显式模板实例化(您可以强制使用-fno-implicit-templates )是一种解决方法; 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: 您可以将您的方法定义放在源文件中(或者,按照Google样式指南的示例,在您不必包含的-inl.h头文件中)然后显式实例化它们,如下所示:

template <class int> class vector;

Note that you don't actually need -fno-implicit-templates to benefit from this; 请注意,您实际上并不需要-fno-implicit-templates来从中受益; 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. 并且添加-fno-implicit-templates将使所有模板更难使用(不仅仅是耗时的模板),所以我不推荐它。

The problem with your example code is that you're not forward declaring the true std::vector class. 您的示例代码的问题在于您没有向前声明真正的std::vector类。 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. 通过不包含<vector> ,你正在创建自己的非标准vector类,并且你永远不会定义push_back ,所以编译器没有任何实例化。

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? 您将所有不变的标头放在一个all.h ,预编译它,并使用strace或类似的方法验证all.h.pch已加载且各个头文件不是? (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.) (如何使用strace :而不是运行g++ mytest.cc ,运行strace -o strace.out g++ mytest.cc ,然后在文本编辑器中查看strace.out并搜索open(调用以查看正在读取的文件)。

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++. 我建议你研究一下预编译头文件的可能性,因为我发现它们确实有助于编译时间,尽管这是在Windows上使用Visual C ++而不是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. 有一个<iosfwd>会给你一些iostream类的前向声明,但总的来说,就前向声明它们而言,你无法做很多关于stl模板的事情。

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. 有关加快编译的其他想法, 请参阅此问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM