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

时间:2015-01-23 16:14:43

标签: 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() {}

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

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

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

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

1 个答案:

答案 0 :(得分:2)

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

#include "child.h"
Child c1;
c1.bar(10.2);

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

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

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

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

Parent *p1 = new Child();

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

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

您遇到的行为是一系列因素:

  1. 编译器如何处理隐式内联函数
  2. 如何为虚拟功能创建虚拟表
  3. 当需要编译器实例化虚拟成员函数时。
  4. 有关详情,请参阅以下问题:

    <强>所以:

    • 似乎允许编译器延迟实例化内联函数直到使用,然后决定它是否想要&#34;内联&#34;他们或创建一个单独的功能。
    • 在使用类之前,编译器不需要完成对象所需的所有实例化
    • 实际上即使在创建对象时,在优化下,编译器也可能没有在类的代码中定义所有内容。

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