代码之家  ›  专栏  ›  技术社区  ›  James McNellis

C++与C++CLI:虚拟函数参数的一致性鉴定

  •  10
  • James McNellis  · 技术社区  · 15 年前

    [使用Visual Studio 2008 SP1测试了以下所有内容]

    在C++中,参数类型的const限定不影响函数的类型(83.5/3:“删除任何修改参数类型的CV限定符”)。

    例如,在下面的类层次结构中, Derived::Foo 重写 Base::Foo :

    struct Base
    {
        virtual void Foo(const int i) { }
    };
    
    struct Derived : Base
    {
        virtual void Foo(int i) { }
    };
    

    在C++/CLI中考虑类似的层次结构:

    ref class Base abstract
    {
    public:
        virtual void Foo(const int) = 0;
    };
    
    ref class Derived : public Base
    {
    public:
        virtual void Foo(int i) override { }
    };
    

    如果我创建一个 Derived :

    int main(array<System::String ^> ^args)
    {
        Derived^ d = gcnew Derived;
    }
    

    它编译时没有错误或警告。当我运行它时,它抛出以下异常,然后终止:

    clrvirtualest.exe中出现“system.typeloadexception”类型的未处理异常

    其他信息:类型“derived”…中的方法“foo”没有实现。

    这个异常似乎表明参数的const限定 影响C++/CLI中函数的类型(或者至少在某种程度上影响重写)。但是,如果我注释掉包含 派生: ,编译器将报告以下错误(在 main 其中 衍生的 已实例化):

    错误C2259:“派生”:无法实例化抽象类

    如果我将const限定符添加到 派生: 或从的参数中删除常量限定符 基地:Foo ,它编译并运行时没有错误。

    我认为如果参数的const限定影响函数的类型,那么如果派生类虚函数中参数的const限定与基类虚函数中参数的const限定不匹配,我就会得到这个错误。

    如果我改变 派生: 的参数 int double ,我得到以下警告(除了上述错误,c2259):

    警告C4490:“override”:重写说明符的使用不正确;“derived::foo”与基引用类方法不匹配

    因此,我的问题是,实际上,函数参数的常量限定是否影响C++/CLI中函数的类型?如果是,为什么要编译它,为什么没有错误或警告?如果不是,为什么抛出异常?

    2 回复  |  直到 10 年前
        1
  •  8
  •   Hans Passant    15 年前

    嗯,这是个虫子。const修饰符通过modopt自定义修饰符发送到元数据中。不幸的是,C++/CLI语言规则与CLI规则不匹配。cli规范的第7.1.1章指出:

    自定义修饰符,使用modreq定义 (必需的修饰符)和modopt (可选修饰符)类似于 自定义属性(§21),除了 修饰语是签名的一部分 而不是依附于 声明。每个modifer associates 中包含项的类型引用 签名。

    cli本身应按要求处理 以及相同的可选修饰符 态度。两个不同的签名 只是增加了一个习惯 修饰语(必需或可选)应 不被认为是匹配的。习俗 修饰符对 可变作用力转向系统的操作。

    因此,CLR称派生::FoE()不是重写,C++/CLI称它是。CLR赢了。

    你可以在connect.microsoft.com上报告这个错误,但这可能是浪费时间。我认为这种不相容是故意的。他们应该改变C++语言和CLI语言规则,但肯定认为C++兼容性更重要。无论如何,cv修饰符都是一个难题,还有其他不受支持的场景,const指针指向const。无论如何,这在运行时无法强制执行,CLR不支持它。

        2
  •  3
  •   Ben Voigt    15 年前

    这是一个bug,它不是特定于C++的。

    https://connect.microsoft.com/VisualStudio/feedback/details/100917/argument-const-ness-is-part-of-member-function-type-signature

    事实上,C++编译器应该剥离顶级的const /易失性。只有指向指针或引用类型的const/volatile才有意义。如果编译器正确地做到了这一点,clr就不会有发言权。

    顺便说一句,这是编译器用/clr:pure生成的il

    .class private abstract auto ansi beforefieldinit Base
        extends [mscorlib]System.Object
    {
        .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
        {
            .maxstack 1
            L_0000: ldarg.0 
            L_0001: call instance void [mscorlib]System.Object::.ctor()
            L_0006: ret 
        }
    
        .method public hidebysig newslot abstract virtual instance void Foo(int32 modopt([mscorlib]System.Runtime.CompilerServices.IsConst)) cil managed
        {
        }
    
    }
    
    .class private auto ansi beforefieldinit Derived
        extends Base
    {
        .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
        {
            .maxstack 1
            L_0000: ldarg.0 
            L_0001: call instance void Base::.ctor()
            L_0006: ret 
        }
    
        .method public hidebysig virtual instance void Foo(int32 i) cil managed
        {
            .maxstack 0
            L_0000: ret 
        }
    
    }
    

    这绝对违反了james列出的删除顶级限定符的规则。

    C++/CLI规范的进一步相关部分:

    8.8.10.1功能覆盖

    [剪辑]

    1. 派生类函数通过使用函数修饰符override显式重写具有相同名称、参数类型列表和cv限定的基类虚拟函数,如果不存在此类基类虚拟函数,则程序的格式将不正确

    12.3声明器类型

    C++标准(第8章3.5/3)扩充如下:
    转换后的参数类型和省略号的存在与否的结果列表是函数参数类型列表。

    因此,我认为,删除CV限定符的规则同样适用于C++/CLI,因为SPEC专门调用ISO标准C++的第83.5/3节。