2

当生成类模板的虚拟方法的代码时,C++ 标准是否说明了确切的时间点?

考虑以下示例:

class Interface
{
  public:
    virtual void f() = 0;
};

template <unsigned int V>
class A : public Interface
{
  public:
    virtual void f()
    {
    }
};

Interface* instantiate()
{
  // class template instantiation with argument V=0
  return new A<0>();
}

// specialization of f() for template argument V=0
template <> void A<0>::f()
{
  cout << "Output from A<0>::f()" << endl;
};

int main() 
{
  Interface* i = instantiate();
  i->f();
  return 0;
}

类模板 A 声明了一个虚方法 f()。在我们的示例中,函数 instantiate() 在 A<0>::f() 的任何显式特化之前隐式实例化类模板 A。在上面的例子中,特化是在类模板 A 的隐式实例化发生之后完成的。现在,至少我的 ARM 编译器和 g++ 选择了 A<0>::f() 的专用版本,即 main() 程序将“从 A<0>::f() 输出”打印到屏幕上。

我能否始终确定,在隐式实例化此类模板之后,定义类模板的虚拟方法的特化就足够了?如果观察到的行为得到 C++ 标准的支持,我会感觉更好。我没有找到任何关于这个话题的明确声明。最接近的部分是 14.7.3/6,它在涉及虚拟方法时有点不明确:

如果一个模板、一个成员模板或一个类模板的成员是显式特化的,那么该特化应在第一次使用该特化之前声明,这将导致隐式实例化发生,在每个翻译单元中使用发生;不需要诊断。如果程序没有为显式特化提供定义,并且该特化的使用方式会导致发生隐式实例化,或者该成员是虚拟成员函数,则该程序是格式错误的,不需要诊断。永远不会为已声明但未定义的显式特化生成隐式实例化

4

2 回答 2

2

我们很确定它是UB。

在实践中:

new A<0>()

将生成对构造函数的调用,并且编译器需要它的定义可用。如果您在此调用后尝试专门A<0>::A()化 gcc 将出错:

error: specialization of ‘A<V>::A() [with V = 0]’ after instantiation

构造函数将具有设置类的多态标头的代码,该标头将包含指向 vtable 的指针。在那个 vtable 中将是 的条目Interface::f,但此时它甚至没有声明最终将填充该插槽的符号,您的显式专业化A<0>::f- 所以它归结为实现质量问题 - 编译器在完成类类型的同时设计 vtable - 如果是这样,它是否能够稍后在 TU 中修复该 vtable 的新声明成员。

于 2013-09-19T16:43:27.490 回答
0

标准在这方面非常不清楚。隐式实例化的相关部分是 14.7.1p2:

[...] 当在需要成员定义存在的上下文中引用特化时,成员的特化被隐式实例化;

不幸的是,“需要定义存在”是一个完全未定义的术语,但我认为可以提出一个很好的论点,即“使用过的 ODR”至少是其中的一个子集。对于“odr-used”,3.2p2 中的大墙文字说:

如果虚成员函数不是纯的,则它是 odr-used 的。

换句话说,虚拟成员仅因其存在而被要求(不是双关语)。

所以我认为可以提出一个论点,即至少允许编译器在实例化包含类的那一刻尝试实例化所有虚函数。我不知道有任何编译器这样做(AFAIK,除非被迫不这样做,否则它们都会延迟实例化直到翻译单元结束),但我认为您的代码严格不符合要求。

于 2013-09-19T15:58:42.733 回答