GCC和MS编译器的模板实例化细节
任何人都可以在GCC和MS编译器的编译和/或链接时提供模板实例如何处理的比较或具体细节? 这个过程在静态库,共享库和可执行文件中是不同的吗? 我发现这个文档有关GCC如何处理它,但我不确定信息是否仍然指的是事物的当前状态。 我应该在编译我的库时使用他们build议的标志,例如-fno-implicit-templates ?
我所知道的(可能不一定是正确的)是:
- 模板将在实际使用时被实例化
- 模板将被实例化为显式实例化的结果
- 通常通过折叠重复实例来处理重复实例化,或者通过推迟实例化直到链接时间
实例化点
模板将在实际使用时被实例化
不完全,但大致。 实例化的精确点有点微妙,我把它委托给Vandevoorde / Josuttis的好书中名为Point of instance的部分。
但是,编译器不一定要正确实现POI: Bug c ++ / 41995:函数模板实例化的错误点
部分实例化
模板将在实际使用时被实例化
这是部分正确的。 对于函数模板来说是正确的,但是对于类模板,只有被使用的成员函数被实例化。 以下是格式正确的代码:
#include <iostream> template <typename> struct Foo { void let_me_stay() { this->is->valid->code. get->off->my->lawn; } void fun() { std::cout << "fun()" << std::endl; } }; int main () { Foo<void> foo; foo.fun(); }
let_me_stay()
在语法上被检查(和语法是正确的),但不是在语义上(即它不被解释)。
两阶段查找
但是,以后只解释相关的代码 ; 显然,在Foo<>
, this
取决于Foo<>
实例化的确切模板ID,所以我们推迟了Foo<>::let_me_alone()
错误检查,直到实例化时间。
但是,如果我们不使用依赖于特定实例的东西,代码必须是好的。 因此,以下是不完善的:
$ cat non-dependent.cc template <typename> struct Foo { void I_wont_compile() { Mine->is->valid->code. get->off->my->lawn; } }; int main () {} // note: no single instantiation
Mine
对于编译器来说是一个完全未知的符号,与this
不同的是编译器可以确定它是实例依赖关系。
这里的关键点是C ++使用两阶段查找模型,在第一阶段它检查非依赖代码,并且在第二阶段(和实例化时间)完成从属代码的语义检查(这是实例化时间)也是一个经常被误解或未知的概念,许多C ++程序员认为模板在实例化之前根本就没有被分析过,但这只是神话的来源,…,Microsoft C ++)。
类模板的完全实例化
Foo<>::let_me_stay()
的定义工作,因为错误检查被推迟到以后,对于this
依赖的指针。 除了你会使用
显式实例
cat > foo.cc #include <iostream> template <typename> struct Foo { void let_me_stay() { this->is->valid->code. get->off->my->lawn; } void fun() { std::cout << "fun()" << std::endl; } }; template struct Foo<void>; int main () { Foo<void> foo; foo.fun(); } g++ foo.cc error: error: 'struct Foo<void>' has no member named 'is'
模板定义以不同的翻译单位
当你明确实例化时,你明确地实例化。 并且让所有的符号对链接器都是可见的,这也意味着模板定义可以驻留在不同的翻译单元中:
$ cat A.cc template <typename> struct Foo { void fun(); // Note: no definition }; int main () { Foo<void>().fun(); } $ cat B.cc #include <iostream> template <typename> struct Foo { void fun(); }; template <typename T> void Foo<T>::fun() { std::cout << "fun!" << std::endl; } // Note: definition with extern linkage template struct Foo<void>; // explicit instantiation upon void $ g++ A.cc B.cc $ ./a.out fun!
但是,您必须显式实例化所有要使用的模板参数,否则
$ cat A.cc template <typename> struct Foo { void fun(); // Note: no definition }; int main () { Foo<float>().fun(); } $ g++ A.cc B.cc undefined reference to `Foo<float>::fun()'
关于两阶段查找的小问题:编译器是否实际实现了两阶段查找并不是由标准决定的。 然而,要符合它,它应该像它一样工作(就像加法或乘法不一定必须使用加法或乘法CPU指令来执行一样。
编辑 :事实certificate,我下面写的是违反C ++标准。 这对于Visual C ++来说是正确的,但对于使用“两阶段名称查找”的编译器是错误的。
据我所知,你说的是对的。 模板在实际使用时将被实例化(包括当被声明为另一个types的成员时,但在函数声明中提到时(不包括正文))或者作为显式实例化的结果。
模板的一个问题是,如果在几个不同的编译单元(.cpp文件)中使用相同的模板(例如vector),编译器会重复在每个.cpp文件中实例化模板的工作,从而减慢编译速度。 IIRC,海湾合作委员会有一些(非标准?)的机制,可以用来避免这一点(但我不使用海湾合作委员会)。 但是Visual C ++总是重复这个工作,除非你在预编译的头文件中使用了显式的模板实例化(但是即使这样也会减慢你的编译速度,因为一个更大的PCH文件需要更长的时间才能加载。)之后,链接器就消除了重复。 注意 :下面的评论链接到一个页面 ,告诉我们并不是所有的编译器都是这样操作的。 一些编译器推迟函数实例化,直到链接时间,这应该是更高效。
第一次使用时,模板没有完全实例化。 特别是,模板中的函数在实际调用之前不会被实例化。 您可以通过向您正在使用的模板添加无意义的函数来轻松validation这一点:
void Test() { fdsh "sw" = 6; wtf? }
除非您明确地实例化模板,或者尝试调用该函数,否则不会出现错误。
我期望静态库(和对象文件)将存储实例化的所有模板的目标代码。 但是如果你的程序有一个特定的静态库作为依赖,那么你实际上不能调用其中已经实例化的模板函数,至less在VC ++中是这样,它总是需要模板类的源代码(包含函数体)为了调用它的function。
我不认为可以在共享库中调用模板函数(当您没有要调用的模板函数的源代码时)。