C++ против C++/CLI: Константная квалификация параметров виртуальной функции

[Все нижеперечисленное было протестировано с использованием Visual Studio 2008 SP1]

В C++ константная квалификация типов параметров не влияет на тип функции (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».

Дополнительная информация: Метод "Foo" типа "Производный"... не имеет реализации.

Это исключение указывает на то, что квалификация const параметра действительно влияет на тип функции в C++/CLI (или, по крайней мере, каким-то образом влияет на переопределение). Однако, если я закомментирую строку, содержащую определение Derived::Foo, компилятор сообщит о следующей ошибке (в строке в main, где создается экземпляр Derived):

ошибка C2259: «Производный»: невозможно создать экземпляр абстрактного класса

Если я добавлю квалификатор const к параметру Derived::Foo или удалю квалификатор const из параметра Base::Foo, он скомпилируется и запустится без ошибок.

Я думаю, что если квалификация const параметра влияет на тип функции, я должен получить эту ошибку, если квалификация const параметра в виртуальной функции производного класса не соответствует квалификации const параметра в виртуальном базовом классе функция.

Если я изменю тип параметра Derived::Foo с int на double, я получу следующее предупреждение (в дополнение к вышеупомянутой ошибке C2259):

предупреждение C4490: «переопределение»: неправильное использование спецификатора переопределения; 'Derived::Foo' не соответствует методу базового класса ref

Итак, мой вопрос заключается в том, влияет ли квалификация const параметров функции на тип функции в C++/CLI? Если да, то почему это компилируется и почему нет ошибок или предупреждений? Если нет, то почему выбрасывается исключение?


person James McNellis    schedule 09.03.2010    source источник
comment
Обновление: я сообщил об этом как об ошибке в Microsoft Connect: connect.microsoft.com/VisualStudio /feedback/details/540788 (я скопировал сюда ссылку из обсуждения ниже, так как она была скрыта в беседе)   -  person James McNellis    schedule 11.03.2010
comment
Член команды компилятора Microsoft C++/CLI 21 марта подтвердил, что это ошибка, и они изучают влияние исправления на существующий код.   -  person Ben Voigt    schedule 22.03.2010
comment
@Бен: Спасибо. Дефект, который я отправил в Microsoft Connect, был закрыт сегодня, так как не будет исправлен. Привет: спасибо, что сообщили об этой проблеме. К сожалению, основываясь на нашем анализе серьезности этой проблемы в сочетании с нашими ограниченными ресурсами, мы не сможем исправить эту проблему в следующем выпуске Visual C++. Ну что ж :-)   -  person James McNellis    schedule 31.03.2010
comment
О, смотри, еще одна мешанина C++/CLI modopt/modreq :) есть еще кое-что, откуда это взялось — взгляни: connect.microsoft.com/VisualStudio/feedback/details/514606/   -  person Pavel Minaev    schedule 08.06.2010


Ответы (2)


Ну, это ошибка. Модификаторы const добавляются в метаданные с пользовательским модификатором modopt. К сожалению, правила языка C++/CLI не совпадают с правилами CLI. В главе 7.1.1 спецификации CLI говорится:

Пользовательские модификаторы, определенные с помощью modreq («обязательный модификатор») и modopt («необязательный модификатор»), аналогичны пользовательским атрибутам (§21), за исключением того, что модификаторы являются частью подписи, а не присоединяются к объявлению. Каждый модификатор связывает ссылку на тип с элементом в сигнатуре.

Сам интерфейс командной строки должен одинаково обрабатывать обязательные и необязательные модификаторы. Две подписи, отличающиеся только добавлением пользовательского модификатора (обязательного или необязательного), не считаются соответствующими. Пользовательские модификаторы никаким другим образом не влияют на работу ВЕС.

Итак, CLR говорит, что Derived::Foo() не является переопределением, C++/CLI говорит, что это так. CLR побеждает.

Вы можете сообщить об ошибке на сайте connect.microsoft.com, но это, вероятно, пустая трата времени. Я думаю, что эта несовместимость была преднамеренной. Они должны были изменить языковые правила для C++/CLI, но наверняка считали, что совместимость с C++ важнее. Модификаторы CV в любом случае доставляют неудобства, есть и другие сценарии, которые недостаточно хорошо поддерживаются, для одного из них const указывает на const. В любом случае это нельзя применить во время выполнения, CLR не поддерживает его.

person Hans Passant    schedule 09.03.2010
comment
Спасибо. Я просмотрел спецификацию C++/CLI, но не подумал заглянуть в спецификацию CLI. Не пользуясь .NET много, я не знал об ограниченной поддержке CLI для квалификации const, пока не начал копаться в этом вопросе. В качестве продолжения, есть ли причина, по которой компилятор C++/CLI не может просто пропустить пользовательский модификатор при создании IL? - person James McNellis; 10.03.2010
comment
Это невозможно, он загружает определения классов из метаданных сборки. Не заголовочные файлы. Эти определения должны быть закодированы с использованием возможностей метаданных. Полный отказ от квалификаторов CV был бы слишком несовместимым. - person Hans Passant; 10.03.2010
comment
Похоже, вы получите другое поведение, если вы просто #включаете заголовок из той же библиотеки, а не вызываете другую DLL... Сумасшествие :( - person Nathan Monteleone; 10.03.2010
comment
Я не так уверен. cv-квалификация параметров функции влияет только на определение функции; это не влияет на тип функции, и размещение cv-квалификаторов в объявлении просто неуместно. Согласно обоснованию Херба Саттера для C++/CLI (§3.5), модификаторы используются [для наполнения] типов CLI расширенной семантикой, специфичной для языка C++. Поскольку в этом случае type не зависит от cv-qualifier, я не понимаю, зачем добавлять cv-qualifier в метаданные (я уверен, что должна быть причина, хотя ... я просто недостаточно знаком с CLI, чтобы понять, почему). - person James McNellis; 10.03.2010
comment
Ключевым моментом является понимание того, для чего используются метаданные. Он заменяет заголовочные файлы. - person Hans Passant; 10.03.2010
comment
В вашем ответе говорится, что C++/CLI не может реализовать правило, как указано, потому что CLR не позволяет этого. Это неправильно и вряд ли имеет значение для ИМО. - person Ben Voigt; 11.03.2010
comment
@Ben: я догадался, что проблема в совместимости между C++/CLI и C++. Я не знаю этого на самом деле, я не посвящен в команду MSFT. Если да, поправьте мой пост. Это было бы положительным вкладом. - person Hans Passant; 11.03.2010
comment
У исходного плаката был ответ на то, почему CLR не виноват в самом верху его вопроса (8.3.5/3: любой cv-qualifier, изменяющий тип параметра, удаляется), и, насколько я могу судить, это было там прежде чем вы ответили. Заявление о том, что компилятор C++ не может удалить атрибут, когда стандарт требует удаления квалификатора, в лучшем случае неискренне. Я не понимал, что вы хотели, чтобы я использовал свои мистические способности MVP, чтобы попросить команду компилятора C ++ / CLI прокомментировать это, на самом деле вы отговорили OP сообщать об ошибке в Microsoft. Но я укажу им на эту тему. - person Ben Voigt; 11.03.2010
comment
Когда я разместил вопрос, я хорошо знал, как С++ обрабатывает типы функций и переопределение. Чего я, однако, не знал, так это того, были ли в C++/CLI другие правила относительно cv-qualification или нет, и была ли это проблема компилятора или среды выполнения. Я не использую .NET на регулярной основе и поэтому признаю, что мало о нем знаю. При этом я думаю, что у компилятора есть два более правильных способа справиться с этим, чем вызвать ошибку времени выполнения: либо квалификаторы должны быть удалены из метаданных, либо компилятор должен запретить несоответствие для типов CLI... - person James McNellis; 11.03.2010
comment
Я не верю, что какое-либо изменение повлияет на соответствие стандартам C++, поскольку оба они влияют только на дополнения C++/CLI к языку. Я по-прежнему не вижу проблемы с удалением квалификаторов, так как они не влияют на определения (хотя многие умные люди приняли решение добавить их, так что я уверен, что есть некоторые причина). В любом случае, я действительно открыл для этого проблему в Connect (connect. microsoft.com/VisualStudio/feedback/details/540788), хотя, вероятно, ему не хватает технической точности, поскольку я все еще пытаюсь понять проблему. - person James McNellis; 11.03.2010
comment
@James: да, но теперь, когда C++/CLI является стандартом ISO, они должны соответствовать ЭТОМУ. А разделы 8.8.10.1 и 12.3 говорят, что применяется стандартное правило C++ для cv-qualifiers. @nobugz: я пытался отменить свой голос несколько комментариев назад, но это не разрешено, как и редактирование вашего поста. Если вы хотите добавить разделы спецификаций в свой пост, я удалю их из своего, они определенно не очень значимы, кроме как в контексте вашего объяснения. - person Ben Voigt; 11.03.2010
comment
@Ben: Думаю, я согласен с тобой в этом. Я думал, что 33.1.5.3 стандарта C++/CLI требует модификатора IsConst, но это не так; Я просто неправильно прочитал это. Спасибо. (@nobugz: Спасибо и вам). - person James McNellis; 11.03.2010

Это ошибка, и она не специфична для C++/CLI.

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

Дело в том, что компилятор C++ должен удалять константу/изменчивость верхнего уровня. Имеет значение только const/volatile для типа указателя или ссылки. Если бы компилятор сделал это правильно, CLR не имела бы права голоса в том, что происходит.

Кстати, это IL, сгенерированный компилятором с параметром /clr:pure.

.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, используя переопределение модификатора функции, при этом программа является неправильно сформированной, если такая виртуальная функция базового класса не существует.

12.3 Типы деклараторов

Стандарт C++ (§8.3.5/3) дополнен следующим образом:
Результирующий список преобразованных типов параметров и наличие или отсутствие многоточия является списком типов параметров функции.

Таким образом, я пришел к выводу, что правило удаления квалификаторов cv применимо и к C++/CLI, потому что спецификация специально ссылается на раздел 8.3.5/3 стандарта ISO C++.

person Ben Voigt    schedule 10.03.2010
comment
Для собственного кода эта ошибка была исправлена, по крайней мере, с VS2008 SP1 (компилятор даже выдает предупреждение, C4373, предыдущие версии компилятора не переопределяли, когда параметры отличались только квалификаторами const/volatile). Я могу воспроизвести проблему, упомянутую в исходном сообщении, только с типами CLI (т. е. типами ref class). - person James McNellis; 11.03.2010
comment
@ Джеймс, я согласен с тем, что мой репродукционный код, наконец, не вызывает проблем с VS2008 SP1 (первое исправление на самом деле не помогло). И я могу воспроизвести вашу проблему, поскольку вы указали ошибку в Connect, я ее проверю. - person Ben Voigt; 11.03.2010