11

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

在 C++ 中,参数类型的 const 限定不影响函数的类型(8.3.5/3:“删除任何修改参数类型的 cv-qualifier”)

因此,例如,在以下类层次结构中,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 限定符添加到参数 ofDerived::Foo或从参数中删除 const 限定符Base::Foo,它将编译并运行而没有错误。

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

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

警告 C4490:“覆盖”:错误使用覆盖说明符;'Derived::Foo' 与基 ref 类方法不匹配

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

4

2 回答 2

9

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

使用 modreq(“必需的修饰符”)和 modopt(“可选的修饰符”)定义的自定义修饰符与自定义属性(第 21 节)类似,只是修饰符是签名的一部分,而不是附加到声明中。每个修饰符将类型引用与签名中的项目相关联。

CLI 本身应以相同的方式处理必需的和可选的修饰符。仅通过添加自定义修饰符(必需或可选)而不同的两个签名不应被视为匹配。自定义修饰符对 VES 的操作没有其他影响。

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

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

于 2010-03-09T23:19:28.790 回答
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 就不会对正在发生的事情发表意见。

顺便说一句,这是编译器使用 /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. 派生类函数通过使用函数修饰符 override 显式覆盖具有相同名称、参数类型列表和 cv 限定的基类虚函数,如果不存在此类基类虚函数,则程序为非良构

12.3 声明符类型

C++ 标准(第 8.3.5/3 节)进行了如下扩充:
转换后的参数类型的结果列表以及省略号的存在与否是函数的参数类型列表。

所以我被引导相信删除 cv 限定符的规则也适用于 C++/CLI,因为规范特别提到了 ISO 标准 C++ 的第 8.3.5/3 节。

于 2010-03-10T20:12:53.477 回答