为什么C++显式实例化的模板方法不能覆盖虚拟方法?



为什么以下代码中的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(centos6)和Clang 3.3(trunk 177401

这只是因为显式实例化来得太晚了,TemplateChild不能被视为非纯虚拟的吗?

编辑:C++11 14.5.2〔temp.mem〕/4说这是不允许专业化的。但我在[temp.extint]部分找不到这么清楚的东西。

4 A specialization of a member function template does not override a virtual function from a base class.

我还编辑了TemplateBounceChild,以匹配C++11草案中该部分使用的示例。

让我们考虑一下如果允许的话会发生什么。

类别CCD_ 1的定义可以存在于多个翻译单元(源文件)中。在这些转换单元中的每一个中,编译器需要能够为TemplateChild生成虚拟函数表(vtable),以确保vtable存在供链接器使用。

vtable表示,"对于动态类型为TemplateChild的对象,这些是所有虚拟函数的最终覆盖。"例如,对于RegularChild,vtable映射两个覆盖RegularChild::method(int)RegularChild::method(float)

TemplateChild::method的显式实例化将只出现在一个翻译单元中,编译器只知道它们存在于该翻译单元中。当编译其他翻译单元时,它不知道显式实例化的存在。这意味着您将为类获得两个不同的vtables:

在存在显式实例化的翻译单元中,将有一个vtable映射两个覆盖TemplateChild::method<int>(int)TemplateChild::method<float>(float)。这没关系。

但是,在没有显式实例化的翻译单元中,您将拥有一个映射到基类虚拟函数的vtable(在您的示例中,这些函数是纯虚拟的;让我们假设存在基类定义)。

您甚至可能有两个以上不同的vtable,例如,如果TemplateChild0和float的显式实例化分别出现在它们自己的翻译单元中。

无论如何,现在我们对同一事物有多个不同的定义,这是一个主要问题。充其量,链接器可能会选择一个并丢弃其余的。即使有某种方法可以告诉链接器选择带有显式实例化的一个,你仍然会遇到编译器可能会使虚拟函数调用失去机会的问题,但要做到这一点,编译器需要知道最终的重写器是什么(实际上,它需要知道vtable中有什么)。

因此,如果允许的话,会出现一些重大问题,并且考虑到C++编译模型,我认为这些问题是不可解决的(至少在C++编译方式没有重大改变的情况下)。

显式实例化只会导致模板被实例化。对于函数模板,这与使用它的效果相同。无论成员函数是显式实例化的还是定期使用的,都没有理由期望任何事情都能以不同的方式工作。

模板专用化无法覆盖非模板函数,因为它们没有相同的名称。专用化是由包含模板参数的模板id命名的。忽略签名中的模板参数,具有不同参数的几个专业化可能具有相同的签名。

如果由于签名与基类虚拟成员一致,该语言希望确定专门化应该是虚拟重写,则必须确定如果使用与某个虚拟函数匹配的某些参数调用函数,则可以推断出所有模板参数。它不能依赖于检查您实际调用函数的方式(由于推导,这看起来像是一个虚拟调度),因为您可以使用模板参数以更模糊的方式调用它,或者根本不调用它(这是您试图使用显式实例化解决的问题)。

N个基类virtual函数和可能与其匹配的M个派生类模板的组合将具有O(N*M)复杂性。在这种情况下,这个特殊功能的规模会很小。

因此,对于每个实际覆盖,最好只使用正则virtual函数声明进行显式处理。(不过,如果能够将这些函数别名在一起,这样它们的地址比较起来就会相等,那可能会很好;尽管指向虚拟成员函数的指针工作方式不同,但相同的函数可能是一种微观优化。)

因为标准是这么说的。参见C++11 14.5.2[temp.mem]/3:

成员功能模板不应是虚拟的。

最新更新