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

时间:2011-08-24 21:14:46

标签: c++ visual-studio templates gcc compiler-construction

是否有人可以提供模板实例化的比较或具体细节 在GCC和MS编译器的编译和/或链接时处理?这个过程是不同的 在静态库,共享库和可执行文件的上下文中? 我发现this doc关于GCC如何处理它,但我不确定是否有这些信息 仍然指的是现状。我应该使用旗帜吗? 他们建议在编译我的库时,例如 -fno隐模板

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

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

2 个答案:

答案 0 :(得分:55)


实例化点

  

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

不完全,但粗略。实例化的确切点有点微妙,我将你委托给Vandevoorde / Josuttis的精美书中名为Point of instantiation的部分。

但是,编译器不一定正确实现POI:Bug c++/41995: Incorrect point of instantiation for function template


部分实例化

  

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

这是部分正确的。对于函数模板也是如此,但对于类模板,只实例化使用的成员函数。以下是格式良好的代码:

#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 ++使用two-phase-lookup的模型,它在第一阶段检查非依赖代码,并且在第二阶段(和实例化时间)完成对依赖代码的语义检查)(这也是一个经常被误解或未知的概念,许多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指令来执行。

答案 1 :(得分:-2)

编辑:事实证明,我在下面写的内容与C ++标准相反。对于Visual C ++,情况确实如此,但对于使用“两阶段名称查找”的编译器则为false。

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

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

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

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

除非您明确地实例化模板,或尝试调用该函数,否则不会收到错误。

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

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