C ++与C ++ / CLI:虚拟函数参数的Const限定

时间:2010-03-09 22:32:13

标签: .net visual-c++ c++-cli virtual-functions

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

在C ++中,参数类型的const限定不影响函数的类型(8.3.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;
}

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

  

ClrVirtualTest.exe中出现未处理的“System.TypeLoadException”类型异常

     

附加信息:'Derived'类型中的方法'Foo'...没有实现。

该异常似乎表明参数的const限定会影响C ++ / CLI中函数的类型(或者,至少它会以某种方式影响覆盖)。但是,如果我注释掉包含Derived::Foo定义的行,编译器将报告以下错误(在main中实例化Derived实例的行上:

  

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

如果我将const限定符添加到Derived::Foo的参数或从Base::Foo的参数中删除const限定符,它将编译并运行,没有错误。

我认为如果参数的const限定条件影响函数的类型,如果派生类虚函数中参数的const限定条件与参数的const限定条件不匹配,我应该得到此错误基类虚函数。

如果我将Derived::Foo的参数类型从int更改为double,我会收到以下警告(除了上述错误,C2259):

  

警告C4490:'覆盖':错误使用覆盖说明符; 'Derived :: Foo'与基本ref类方法

不匹配

所以,我的问题是,有效的是,函数参数的const限定是否会影响C ++ / CLI中函数的类型?如果是这样,为什么这会编译,为什么没有错误或警告?如果没有,为什么会抛出异常?

2 个答案:

答案 0 :(得分:8)

嗯,这是一个错误。 const修饰符使用modopt自定义修饰符发送到元数据中。遗憾的是,C ++ / CLI语言规则与CLI规则不匹配。 CLI规范的第7.1.1节说:

  

使用modreq定义的自定义修改器   (“必需修饰符”)和modopt   (“可选修饰符”),类似于   自定义属性(§21)除外   修饰符是签名的一部分   而不是依附于   adeclaration。每个修饰者都有联系   带有项目的类型引用   签名。

     

CLI本身应该要求处理   和可选的修饰符相同   方式。两个不同的签名   只能通过添加自定义   修饰语(必需或可选)   不被认为是匹配。习惯   修饰符对其没有其他影响   VES的运作。

因此,CLR说Derived :: Foo()不是覆盖,C ++ / CLI说它是。 CLR获胜。

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

答案 1 :(得分:3)

这是一个错误,并不是特定于C ++ / CLI。

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

事实上,C ++编译器应该剥离顶级const / volatile。指针或引用的指向类型上只有const / volatile很重要。如果编译器正确地做到了这一点,那么CLR就不会对发生的事情发表意见。

BTW这是编译器用/ 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 
    }

}

这绝对违反了詹姆斯列出的关于删除顶级资格赛的规则。

C ++ / CLI规范的其他相关部分:

  

8.8.10.1功能覆盖

     

[剪断]

     
      
  1. 派生类函数通过使用函数修饰符覆盖显式覆盖具有相同名称,参数类型列表和cv限定的基类虚函数,如果没有这样的基类虚函数,则程序格式错误功能存在
  2.         

    12.3声明者类型

         

    C ++标准(§8.3.5/ 3)增加了如下:
      生成的转换参数类型列表以及省略号的存在与否是函数的参数类型列表。

因此我认为删除cv-qualifiers的规则也适用于C ++ / CLI,因为该规范特别指出了ISO标准C ++的8.3.5 / 3节。