C++成员函数模板的隐式实例化

Tom*_*Tom 9 c++ templates

我想更好地了解编译器何时隐式实例化成员函数模板.

请考虑以下示例:

// 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() {}
Run Code Online (Sandbox Code Playgroud)

通过编译[g++|clang++] -c example.cpp,GCC和clang都隐式实例化了函数模板Child::bar<double>.但是,以下看似微小的变化阻止了这一点:

  • 制作foo不虚拟
  • 使Child不继承Parent
  • 删除 baz
  • 制作baz不虚拟
  • baz使用标题中的声明进行定义

是否有关于何时进行隐式实例化的合理简洁解释,还是需要涉及标准的副本?我搜索了SO以寻找与隐式实例化有关的其他问题,但没有找到太多.我找到的最接近的是这个问题,但它是关于类模板中的虚函数(不是成员函数模板).

要明确:我理解通过在头文件中包含定义或显式实例化我需要的模板可以避免此问题.在挖掘为什么一个类(我无意中省略了它的明确实例)仍然愉快地编译和链接时,这仅仅是出于好奇点.

小智 4

我已将您的代码放入 Visual Studio 2013(child.h 和 child.cpp)并尝试了以下操作:

#include "child.h"
Child c1;
c1.bar(10.2);
Run Code Online (Sandbox Code Playgroud)

这会产生未解决的外部错误,表明没有发生“隐式实例化”。表明在这种情况下 Visual Studio 和 G++ 之间存在明显差异。通过将 Child::foo 的代码移动到 child.cpp 文件中可以修复此问题。

因此,简短的答案是: 您所经历的在很大程度上是特定于编译器的,为了可移植性,您不应该依赖这种行为。根据 C++ 标准,最安全的方法是将模板定义放入 .h(或 .hpp)文件中,或者根据需要显式实例化模板。任何其他管理此问题的方法都可能会在某些编译器中出现故障。

要进一步了解 Visual Studio 的行为,请查看以下内容:

也就是说,类定义中定义的任何函数都是隐式内联的。如果编译器选择的话,它可以延迟内联函数的实例化。这意味着在编译 child.obj 时,永远不会创建 Child::foo(d) ,这又意味着 bar 永远不会实例化,因此这会导致链接阶段的编译问题。考虑到 foo(double) 在技术上是一个虚函数,为什么 Visual Studio 实际上可以做到这一点确实很奇怪,但 Visual Studio 似乎将 foo() 的实例化留到以后使用 Child 时。例如:

Parent *p1 = new Child();
Run Code Online (Sandbox Code Playgroud)

还会导致问题,因为此时编译器尝试创建 Child::foo(double) 但由于缺少模板定义而无法创建。

根据您的结果,假设 GCC 将立即实例化内联函数(如果它们是虚拟的)。

您所经历的行为是多种因素的组合:

  1. 编译器如何处理隐式内联函数
  2. 如何为虚拟功能创建虚拟表
  3. 而当编译器需要实例化虚拟成员函数时。

请参阅这些问题以获取更多信息:

所以:

  • 似乎编译器可以延迟内联函数的实例化,直到使用为止,然后决定是否要“内联”它们或创建一个单独的函数。
  • 在使用类之前,编译器不需要完成对象所需的所有内容的实例化
  • 事实上,在优化下,即使在创建对象时,编译器也可能没有准备好类代码中定义的所有内容。

我决定不详细回答您的具体问题,因为我的研究似乎表明您的代码工作是间接的,并且这种行为不应该依赖。