Поделиться через


Тип перечисления Enum в среде CLR

В Visual C++ 2010 объявление и поведение типов перечисления Enum изменилось по сравнению с управляемыми расширениями для C++.

В управляемых расширениях объявление типа Enum предварялось ключевым словом __value. Это было необходимо для того, чтобы различать собственное перечисление и перечисление CLR, которое является производным от класса System::ValueType, хотя оба перечисления выполняют аналогичные функции. Пример:

__value enum e1 { fail, pass };
public __value enum e2 : unsigned short  { 
   not_ok = 1024, 
   maybe, ok = 2048 
};

В новом синтаксисе проблема отличия собственного перечисления от перечисления CLR решена с помощью акцента на природе класса перечисления CLR, а не его происхождении от типа значения. По сути, ключевое слово __value в объявлении заменено на составное ключевое слово enum class. Оно предоставляет альтернативное составное ключевое слово для объявления классов ссылок, значений и интерфейсов.

enum class ec;
value class vc;
ref class rc;
interface class ic;

В новом синтаксисе пара перечисления e1 и e2 выглядит следующим образом:

enum class e1 { fail, pass };
public enum class e2 : unsigned short { 
   not_ok = 1024,
   maybe, ok = 2048 
};

Кроме этого незначительного изменения синтаксиса изменилось и поведение типа перечисления Enum в CLR:

  • Больше не поддерживается предварительное объявление типа перечисления Enum. Отсутствует сопоставление. Он просто помечается флагом как ошибка времени компиляции.
__value enum status; // Managed Extensions: ok
enum class status;   // new syntax: error
  • Разрешение перегрузки между встроенными арифметическими типами и иерархией классов Object изменилось в новой версии языка. В качестве побочного эффекта типы перечисления CLR не могут больше неявно преобразовываться в арифметические типы.

  • В новом синтаксисе перечисление CLR имеет собственную область действия, чего не было в управляемых расширениях. Ранее перечислители отображались в области, содержащей перечисление. Теперь перечислители инкапсулируются в области перечисления.

Перечисления CLR как подвид Object

Рассмотрим следующий фрагмент кода:

__value enum status { fail, pass };

void f( Object* ){ Console::WriteLine("f(Object)\n"); }
void f( int ){ Console::WriteLine("f(int)\n"); }

int main()
{
   status rslt = fail;

   f( rslt ); // which f is invoked?
}

Для программистов, работающих с машинным C++, естественным ответом на вопрос, какой экземпляр перегруженной функции f() вызывается, является f(int). Перечисление является символьной целой константой и используется в стандартных целых повышениях, которые в данном случае имеют приоритет. В управляемых расширениях использовался экземпляр, к которому разрешался вызов. Это привело к неожиданным последствиям, не при использовании в машинном C++, а при необходимости использовать их во взаимодействии с существующей инфраструктурой BCL (библиотеки базовых классов), в которой Enum является классом, неявно производным от класса Object. При разработке на языке Visual C++ 2010 экземпляр f() вызывается как экземпляр f(Object^).

Это происходит благодаря тому, что в Visual C++ 2010 не поддерживается неявное преобразование между типом перечисления CLR и арифметическими типами. Это означает, что для любого назначения объекта типа перечисления CLR арифметическому типу потребуется выполнить неявное приведение типов. Так, например, при использовании

void f( int );

в качестве неперегруженного метода в управляемых расширениях вызов

f( rslt ); // ok: Managed Extensions; error: new syntax

будет выполнен без ошибок, и значение, содержащееся в rslt, будет неявно преобразовано в целое число. В Visual C++ 2010 при выполнении данного вызова произойдет ошибка. Для правильного перевода необходимо вставить оператор преобразования.

f( safe_cast<int>( rslt )); // ok: new syntax

Область действия типа перечисления CLR

Одним из изменений в C++ по сравнению с языком C является появление области действия в структуре. В языке C структура являлась статическим выражением данных без поддержки интерфейса или связанной области действия. Тогда это было радикальным изменением и вызывало много вопросов у пользователей, которые переходили с языка C на C++. Аналогичным образом можно описать связь между собственным перечислением и перечислением CLR.

В управляемых расширениях была предпринята попытка определить слабоинъективные имена перечислителей для перечисления CLR, чтобы смоделировать отсутствие области действия для собственного перечисления. Эта попытка оказалась неудачной. Проблема заключается в том, что перечислители сливаются с глобальным пространством имен, что, в свою очередь, усложняет управление конфликтами имен. В новом синтаксисе (как и в других языках среды CLR) присутствует поддержка области действия в перечислениях CLR.

Это означает, что любое неопределенное использование перечислителя перечисления CLR не будет распознаваться в новом синтаксисе. Рассмотрим следующий реальный пример:

// Managed Extensions supporting weak injection
__gc class XDCMake {
public:
   __value enum _recognizerEnum { 
      UNDEFINED,
      OPTION_USAGE, 
      XDC0001_ERR_PATH_DOES_NOT_EXIST = 1,
      XDC0002_ERR_CANNOT_WRITE_TO = 2,
      XDC0003_ERR_INCLUDE_TAGS_NOT_SUPPORTED = 3,
      XDC0004_WRN_XML_LOAD_FAILURE = 4,
      XDC0006_WRN_NONEXISTENT_FILES = 6,
   };

   ListDictionary* optionList;
   ListDictionary* itagList;

   XDCMake() {
      optionList = new ListDictionary;

      // here are the problems …
      optionList->Add(S"?", __box(OPTION_USAGE)); // (1)
      optionList->Add(S"help", __box(OPTION_USAGE)); // (2)

      itagList = new ListDictionary;
      itagList->Add(S"returns", 
         __box(XDC0004_WRN_XML_LOAD_FAILURE)); // (3)
   }
};

Каждый из трех вариантов неопределенного использования имен перечислителя ((1), (2) и (3)) должен быть определен в новом синтаксисе для компиляции исходного кода. Ниже приводится правильный перевод первоначального исходного кода.

ref class XDCMake {
public:
   enum class _recognizerEnum {
      UNDEFINED, OPTION_USAGE, 
      XDC0001_ERR_PATH_DOES_NOT_EXIST = 1,
      XDC0002_ERR_CANNOT_WRITE_TO = 2,
      XDC0003_ERR_INCLUDE_TAGS_NOT_SUPPORTED = 3,
      XDC0004_WRN_XML_LOAD_FAILURE = 4,
      XDC0006_WRN_NONEXISTENT_FILES = 6
   };

   ListDictionary^ optionList;
   ListDictionary^ itagList;

   XDCMake() {
      optionList = gcnew ListDictionary;
      optionList->Add("?",_recognizerEnum::OPTION_USAGE); // (1)
      optionList->Add("help",_recognizerEnum::OPTION_USAGE); //(2)
      itagList = gcnew ListDictionary;
      itagList->Add( "returns", 
         _recognizerEnum::XDC0004_WRN_XML_LOAD_FAILURE); //(3)
   }
};

Это приводит к изменению стратегии разработки с помощью собственных перечислений по сравнению с перечислениями CLR. Поскольку перечисление CLR поддерживает связанную область действия в Visual C++ 2010, инкапсуляция объявления перечисления внутри класса не является ни необходимой, ни эффективной. Эта особенность языка появилась во время разработки CFront 2.0 в компании Bell Laboratories и была призвана решить проблему "загрязнения" глобального пространства имен.

В первоначальном бета-выпуске новой библиотеки Iostream, разработанной Дж. Шварцем (Bell Laboratories), все связанные перечисления, определенные для библиотеки, не были инкапсулированы. Поэтому общие перечислители, такие как read, write, append и т. п., сделали практически невозможной компиляцию с использованием существующего кода. Решением этой проблемы могло бы стать искажение имен, такое как io_read, io_write и т. д. Другим возможным вариантом могло бы стать изменение языка путем добавления области действия к перечислению, но в тот момент этот вариант казался неосуществимым. Компромиссным решением стала инкапсуляция перечисления внутри класса или иерархии классов, при которой область действия класса заполняется именем тега и перечислителями для перечисления. Таким образом, причина помещения перечислений внутри классов была не философской, по крайней мере, изначально. Это был практический ответ на проблему "загрязнения" глобального пространства имен.

При наличии перечисления Visual C++ 2010 инкапсуляция перечисления внутри класса более не имеет прежних преимуществ. Более того, при рассмотрении пространств имен System можно увидеть, что перечисления, классы и интерфейсы находятся в одном и том же пространстве объявлений.

См. также

Ссылки

enum class

Основные понятия

Типы значений и их режимы работы