GCC和MS编译器的模板实例化细节

Template instantiation details of GCC and MS compilers

本文关键字:实例化 细节 MS 编译器 GCC      更新时间:2023-10-16

谁能提供一个比较或具体的细节如何模板实例化在GCC和MS编译器的编译和/或链接时间处理?这个过程不同吗?在静态库、共享库和可执行文件的上下文中?我找到了这个关于GCC如何处理它的文档,但我不确定是否有信息仍然是指事物的当前状态。我应该使用旗子吗他们建议在编译我的库时使用-fno-implicit-templates?

我所知道的(可能不一定是正确的)是:

  • 模板将在实际使用时实例化
  • 模板将作为显式实例化的结果被实例化
  • 重复实例化通常通过折叠重复实例化来处理,或者将实例化延迟到链接时间

实例化点

模板将在实际使用时实例化

不完全是,但大致是。实例化的精确点有点微妙,我把你委托给Vandevoorde/Josuttis的书中名为实例化点的部分。

但是,编译器不一定正确地实现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指令执行一样)。

Edit:原来我下面写的是违背c++标准的。对于visualc++是正确的,但是对于使用"两阶段名称查找"的编译器是错误的。

就我所知,你说的是对的。模板将在实际使用时被实例化(包括作为另一类型的成员声明时,但在函数声明(没有函数体)中提到时不被实例化)或作为显式实例化的结果。

模板的一个问题是,如果你在几个不同的编译单元(.cpp文件)中使用相同的模板(例如vector),编译器会在每个。cpp文件中重复实例化模板的工作,从而减慢编译速度。GCC有一些(非标准的?)机制可以用来避免这种情况(但我不使用GCC)。但是Visual c++总是重复这项工作,除非您在预编译头文件中使用显式模板实例化(但即使这样也会减慢编译速度,因为更大的PCH文件需要更长的加载时间)。之后,链接器就会消除重复项。注释:下面的注释链接到一个页面,告诉我们不是所有的编译器都这样操作。有些编译器将函数实例化延迟到链接时,这应该更有效。

模板在第一次使用时没有被完全实例化。特别是,模板中的函数在实际调用之前不会被实例化。您可以通过在正在使用的模板中添加一个无意义的函数来验证这一点:

void Test() { fdsh "s.w" = 6; wtf? }

你不会得到一个错误,除非你显式实例化模板,或者尝试调用该函数。

我期望静态库(和目标文件)将存储所有实例化模板的目标代码。但是,如果你的程序有一个特定的静态库作为依赖,你实际上不能调用已经在其中实例化的模板函数,至少在vc++中不能,它总是需要模板类的源代码(带函数体)才能调用其中的函数。

我认为在共享库中调用模板函数是不可能的(当你没有你想调用的模板函数的源代码时)