为什么以下代码中的TemplateChild不起作用?我知道虚方法不能是模板,但为什么显式实例化模板方法不能覆盖虚方法?
#include <iostream>
class VirtBase
{
public:
VirtBase() {};
virtual ~VirtBase() {};
virtual void method( int input ) = 0;
virtual void method( float input ) = 0;
};
class RegularChild : public VirtBase
{
public:
RegularChild() {};
~RegularChild() {};
void method( int input ) {
std::cout << "R" << input << std::endl;
}
void method( float input ) {
std::cout << "R" << input << std::endl;
}
};
class TemplateBounceChild : public VirtBase
{
public:
TemplateBounceChild() {};
~TemplateBounceChild() {};
void method( int input ) {
this->method<>( input );
}
void method( float input ) {
this->method<>( input );
}
template< typename INPUT >
void method( INPUT input ) {
std::cout << "B" << input << std::endl;
};
};
class TemplateChild : public VirtBase
{
public:
TemplateChild() {};
~TemplateChild() {};
template< typename INPUT >
void method( INPUT input ) {
std::cout << "T" << input << std::endl;
};
};
template void TemplateChild::method< int >( int );
template void TemplateChild::method< float >( float );
int main( int, char**, char** )
{
int i = 1;
float f = 2.5f;
VirtBase * v;
RegularChild r;
v = &r;
r.method( i );
r.method( f );
v->method( i );
v->method( f );
TemplateChild c; // TemplateBounceChild here works correctly.
v = &c;
c.method( i );
c.method( f );
v->method( i );
v->method( f );
return 0;
}
gcc 4.4.7(CentOS 6)和Clang 3.3(主干177401)同意在TemplateChild中没有实现这两个纯虚方法,尽管在编译时此时,TemplateChild显式有一个名为'method'的方法, float,以及一个名为'method'的方法,它接受一个int。
仅仅因为显式实例化已经来不及将TemplateChild视为非纯虚拟吗?
编辑:C ++ 11 14.5.2 [temp.mem] / 4表示专业化不允许这样做。但是我在[temp.explicit]部分找不到任何关于同样事情的内容。
4 A specialization of a member function template does not override a virtual function from a base class.
我还编辑了TemplateBounceChild,以匹配C ++ 11草案该部分中使用的示例。
答案 0 :(得分:7)
让我们考虑如果允许这会发生什么。
类TemplateChild
的定义可能出现在多个翻译单元(源文件)中。在每个翻译单元中,编译器需要能够为TemplateChild
生成虚函数表(vtable),以确保链接器存在vtable。
vtable说,“对于动态类型为TemplateChild
的对象,这些是所有虚函数的最终覆盖。”例如,对于RegularChild
,vtable会映射两个覆盖RegularChild::method(int)
和RegularChild::method(float)
。
TemplateChild::method
的显式实例化只会出现在一个翻译单元中,编译器只知道它们存在于那个翻译单元中。在编译其他翻译单元时,它不知道显式实例化存在。这意味着您最终将为该类提供两个不同的 vtable:
在存在显式实例化的翻译单元中,您将拥有一个映射两个覆盖TemplateChild::method<int>(int)
和TemplateChild::method<float>(float)
的vtable。这没关系。
但是在没有显式实例化的翻译单元中,你将有一个映射到基类虚函数的vtable(在你的例子中是纯虚函数;让我们假装有基类定义)。 / p>
您甚至可能有两个以上不同的vtable,例如如果int
和float
的显式实例化都出现在他们自己的翻译单元中。
在任何情况下,现在我们对同一件事有多个不同的定义,这是一个主要问题。链接器最多可以选择一个并丢弃其余的。即使有某种方法告诉链接器选择具有显式实例化的那个,你仍然会遇到编译器可能虚拟化虚函数调用的问题,但为了做到这一点,编译器需要知道最终的覆盖是什么(实际上,它需要知道vtable中的内容)。
因此,如果允许这样做会出现一些重大问题,并且考虑到C ++编译模型,我不认为这些问题是可解决的(至少在没有对C ++编译方式进行重大更改的情况下也是如此)。 / p>
答案 1 :(得分:4)
显式实例化只会导致模板被实例化。对于函数模板,这与使用它的效果相同。无论成员函数是明确实例化还是定期使用,都没有理由期望任何工作方式不同。
模板特化无法覆盖非模板函数,因为它们的名称不同。特化由template-id命名,包括模板参数。忽略签名中的模板参数,具有不同参数的几个特化可以具有相同的签名。
如果语言想要确定专门化应该是虚拟覆盖,因为签名与基类虚拟成员重合,则必须确定如果使用某些参数调用该函数,则可以推导出所有模板参数匹配一些虚函数。它无法依赖于检查您实际调用函数的方式(由于演绎而看起来像虚拟调度),因为您可以使用模板参数以更加模糊的方式调用它,或者根本不调用它(这是您尝试使用显式实例化的问题。)
N个基类virtual
函数和可能与它们匹配的M个派生类模板的组合将具有O(N * M)复杂度。在这种情况下,特殊功能会缩小。
因此,最好只使用每个实际覆盖的常规virtual
函数声明。 (或许能够将这些函数组合在一起,因此它们的地址比较相等,但是;尽管指向虚拟成员函数的指针的工作方式不同,但相同的函数可能是微优化。)
答案 2 :(得分:3)
因为标准说的是这样。参见C ++ 11 14.5.2 [temp.mem] / 3:
成员函数模板不应是虚拟的。