【问题标题】:Template instantiation details of GCC and MS compilersGCC 和 MS 编译器的模板实例化细节
【发布时间】:2011-11-03 04:27:19
【问题描述】:

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

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

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

【问题讨论】:

  • 现在是看起来像个傻瓜的时刻(我一生的故事):我认为反对票只是为了解释赏金(因为我立即减去了 50 声望当我“丰富”的问题)。
  • 我不认为赏金会改变问题的“价值”。
  • 嗯,微软没有为初学者正确实现两阶段查找。 (这是委婉说法:根本没有两阶段查找)

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


【解决方案1】:

实例化点

模板在实际使用时会被实例化

不完全是,但大致上。实例化的精确点有点微妙,我将您委托给 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&lt;&gt; 内,this 取决于实例化Foo&lt;&gt; 的确切模板ID,因此我们将Foo&lt;&gt;::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&lt;&gt;::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’


不同翻译单位的模板定义

当你显式实例化时,你显式实例化。并使 all 符号对链接器可见,这也意味着模板定义可能驻留在不同的翻译单元中:

$ 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 指令来执行。

【讨论】:

  • 关于依赖与非依赖的非常好的观点(我当然没有意识到这一点)。我刚刚检查了以下代码: template struct Foo { void I_wont_compile() { this->is->valid->code.下车->下车->我的->草坪; } void Test() { fdsh "s.w" = 6;什么? } }; int main () { } 它在 VC++ 2008 中编译良好,但在 GCC 中无法编译。有谁知道为什么 VC++ 会这样?(可能是因为编译时产生的开销?)
  • VC++ 过去有更多的问题。您可以浏览 boost-sources 并查看许多与 VC++(旧版本)相关的解决方法。我知道的另一个错误是您可以与模板参数成为朋友,这很适合在 C# 中实现 final 类,但实际上是无效的 C++:codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4143。
  • 好吧,赏金已经过期,但我没有机会真正奖励它,因为我已经有几天没有在网站上活跃了。我不确定如何进行。我应该打开另一个(我认为这个答案值得赏金)?
【解决方案2】:

编辑:原来我在下面写的和C++标准是相悖的。对于 Visual C++ 是正确的,但对于使用“两阶段名称查找”的编译器是错误的。

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

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

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

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

除非显式实例化模板或尝试调用函数,否则不会出现错误。

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

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

【讨论】:

  • 连我的编辑都知道代码很臭。你试过了吗?代码将被完全解析,但标识符未解析(许多语义检查被推迟到实例化时间)。有时出于这个原因,您必须消除模板参数依赖名称的歧义(例如著名的 typename 关键字); 在实例化时,替换的语义错误不是致命的(google SFINAE)
  • 是的,我刚试过。没有编译器错误,因为我不在任何地方调用 Test()。模板函数可以理解为美化的宏。就像宏一样,不使用它就不会出错。
  • @Qwertie:别抱太大希望:导出功能已从标准中删除;另见stackoverflow.com/questions/2441886/…
  • @sehe:您混淆了 export 和 extern,它很少使用并且已被删除,它声明模板已经在另一个翻译单元中实例化。参见“[temp.explicit]”第 2 段。
  • 又过了一年,这个答案仍然是错误的。抱歉,我要举报这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-19
  • 1970-01-01
  • 1970-01-01
  • 2014-12-07
  • 1970-01-01
  • 1970-01-01
  • 2021-08-21
相关资源
最近更新 更多