我必须处理一个由许多模板化类组成的库,这些类当然都是在头文件中实现的。现在我正在努力找到一种方法来减少无法忍受的长编译时间,因为我几乎必须在每个编译单元中包含整个库。
使用前向声明是否有可能,尽管有模板?我正在尝试下面的示例中的某些内容,我尝试绕过#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:有没有办法让上面的例子实际编译和链接正确? Logan建议使用-fno-implicit-templates
并将template class std::vector<int>
放在某处,大概是一个用.cpp
编译的单独的-fno-implicit-templates
文件,但我仍然会遇到链接器错误。再一次,我试图理解它如何适用于std::vector
,以便我可以将它应用于我实际使用的模板化类。
答案 0 :(得分:35)
你无法转发声明类的“部分”。即使你可以,你仍然需要在某处实例化代码,以便你可以链接它。有办法处理它,你可以使自己成为一个带有常见容器实例的小型库(例如vector)并将它们链接起来。然后你只需要编译例如矢量&lt;诠释&GT;一旦。要实现这一点,你需要使用像-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;
}
所以现在我可以使用-fno-implicit-templates
编译a.cpp:
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
我们得到b.o.链接a.o和b.o我们可以得到
g++ a.o b.o
万岁,没有链接器错误。
因此,要了解有关更新问题的一些细节,如果这是一个家庭酿造的课程,它不一定非常混乱。例如,您可以将接口与实现分开,例如说除了a.cpp和b.cpp
之外,我们还有c.h,c.cppc.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编译成b.o一次。当a.cpp发生变化时,您只需重新编译并链接到b.o。
答案 1 :(得分:22)
前向声明允许您这样做:
template <class T> class vector;
然后,您可以声明对vector<whatever>
的引用和指针,而无需定义向量(不包括vector
的头文件)。这与常规(非模板)类的前向声明相同。
模板的问题尤其在于,您通常不仅需要类声明,还需要头文件中的所有方法定义(以便编译器可以实例化所需的模板)。显式模板实例化(您可以强制使用-fno-implicit-templates
)是一种解决方法;您可以将方法定义放在源文件中(或者,在Google Style Guide的示例中,在您不必包含的-inl.h
头文件中),然后显式实例化它们,如下所示: / p>
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(
并搜索{{1}}次调用以查看哪些文件正在阅读。)
答案 2 :(得分:6)
使用前向声明,您只能将成员或参数声明为指向该类型的指针或引用。您不能使用任何需要所述类型内部的方法或其他东西。这就是说我发现前向声明在尝试加快编译时间时确实存在限制。我建议你更多地研究预编译头文件的可能性,因为我发现它们确实有助于编译时间,尽管那是在Windows上使用Visual C ++而不是g ++。
答案 3 :(得分:3)
有<iosfwd>
会给你一些iostream类的前向声明,但总的来说,就前向声明它们而言,你无法对stl模板做些什么。
预编译标题是可行的方法。第一次编译它们时,您不会注意到任何速度提升,但每次修改预编译标题(或其中包含的任何内容)时,您只需支付一次该价格。
See this question关于加快编译的其他想法。