我想更好地了解编译器何时隐式实例化成员函数模板。
考虑以下示例:
// example.h
struct Parent {
virtual void foo(double) {}
};
struct Child : Parent {
void foo(double d) { bar(d); }
template<typename T> void bar(T);
virtual void baz();
};
// example.cpp
#include "example.h"
template <typename T>
void Child::bar(T) {}
void Child::baz() {}
用 [g++|clang++] -c example.cpp
编译它, GCC 和 clang 都隐式实例化函数模板 Child::bar<double>
.然而,以下看似微小的变化阻止了这一点:
- 制作
foo
不是虚拟的 - 制作
Child
不继承Parent
- 删除
baz
- 制作
baz
不是虚拟的 - 定义
baz
在 header 中声明
对于隐式实例化何时发生,是否有任何合理简洁的解释,或者我是否需要费力地阅读标准的拷贝?我在 SO 中搜索了与隐式实例化相关的其他问题,但没有找到太多。我找到的最接近的是 this question ,但它是关于类模板(不是成员函数模板)中的虚函数。
明确一点:我知道可以通过在 header 中包含定义或显式实例化我需要的模板来避免这个问题。在深入研究为什么一个类(我无意中忽略了其显式实例化)仍然可以愉快地编译和链接时,这只是出于好奇。
最佳答案
我已将您的代码放入 Visual Studio 2013(child.h 和 child.cpp)并尝试了以下操作:
#include "child.h"
Child c1;
c1.bar(10.2);
这会产生一个 Unresolved external 错误,表明没有发生“隐式实例化”。表明在这种情况下 Visual Studio 和 G++ 之间存在明显的区别。 这是通过将 Child::foo 的代码移动到 child.cpp 文件中解决的。
因此简短的回答是: 您遇到的情况在很大程度上是特定于编译器的,为了可移植性,您不应该依赖这种行为。 根据 C++ 标准,最安全的方法是将模板定义放在 .h(或 .hpp)文件中,或者根据需要显式实例化模板。任何其他管理此问题的方法都可能会破坏某些编译器。
要进一步了解 Visual Studio 的行为,请查看以下内容:
- https://msdn.microsoft.com/en-us/library/aa299373%28v=vs.60%29.aspx
- https://isocpp.org/wiki/faq/inline-functions#inline-member-fns
也就是说,在类的定义中定义的任何函数都是隐式内联的。编译器可以选择延迟内联函数的实例化。这意味着在编译 child.obj 时 Child::foo(d) 永远不会被创建,这反过来意味着 bar 永远不会被实例化,所以这会导致链接阶段的编译问题。 考虑到 foo(double) 在技术上是一个虚函数,为什么 visual Studio 实际上可以做到这一点确实很奇怪,但似乎 Visual Studio 将 foo() 的实例化留给以后使用 Child 时使用。 例如:
Parent *p1 = new Child();
也会导致一个问题,因为此时编译器试图创建 Child::foo(double) 但由于缺少模板定义而无法创建。
根据您的结果,假设 GCC 将立即实例化内联函数(如果它们是虚拟的)。
您遇到的行为是多种因素的结合:
- 编译器如何处理隐式内联函数
- 如何为虚函数创建虚表
- 以及当编译器需要实例化虚成员函数时。
查看这些问题以获取更多信息:
- When is a vtable created in C++?
- Is "inline" implicit in C++ member functions defined in class definition
所以:
- 似乎允许编译器延迟内联函数的实例化直到使用,然后决定是否要“内联”它们或创建一个单独的函数。
- 在使用类之前,编译器不需要完成对象所需的所有实例
- 事实上,即使在创建对象时,编译器也可能没有为类准备好在代码中定义的所有内容。
我决定不详细回答你的具体问题,因为我的研究似乎表明你的代码工作是偶然的,这种行为不应该被依赖。
关于C++成员函数模板的隐式实例化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28114087/