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

时间:2013-12-11 08:06:13

标签: c++ templates virtual-inheritance

为什么以下代码中的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草案该部分中使用的示例。

3 个答案:

答案 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,例如如果intfloat的显式实例化都出现在他们自己的翻译单元中。

在任何情况下,现在我们对同一件事有多个不同的定义,这是一个主要问题。链接器最多可以选择一个并丢弃其余的。即使有某种方法告诉链接器选择具有显式实例化的那个,你仍然会遇到编译器可能虚拟化虚函数调用的问题,但为了做到这一点,编译器需要知道最终的覆盖是什么(实际上,它需要知道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:

  

成员函数模板不应是虚拟的。