Я пытаюсь сделать что-то вроде следующего:
enum E;
void Foo(E e);
enum E {A, B, C};
который компилятор отклоняет. Я бегло просмотрел Google и пришел к выводу, что «вы не можете этого сделать», но я не могу понять почему. Кто-нибудь может объяснить?
Разъяснение 2: я делаю это, поскольку у меня есть частные методы в классе, которые принимают указанное перечисление, и я не хочу, чтобы значения перечисления отображались, поэтому, например, я не хочу, чтобы кто-либо знал, что E определяется как
enum E {
FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}
поскольку проект X - это не то, о чем я хочу, чтобы мои пользователи знали.
Итак, я хотел переслать объявление перечисления, чтобы я мог поместить частные методы в файл заголовка, объявить перечисление внутри cpp и распространить созданный файл библиотеки и заголовок среди людей.
Что касается компилятора - это GCC.
Если вы прочитаете принятый ответ, это вполне возможно, начиная с C++ 11.





Кажется, это не может быть объявлено вперед в GCC!
Интересное обсуждение - здесь.
На самом деле не существует такой вещи, как предварительное объявление enum. Поскольку определение перечисления не содержит кода, который мог бы зависеть от другого кода, использующего перечисление, обычно не проблема полностью определить перечисление при первом его объявлении.
Если ваше перечисление используется только частными функциями-членами, вы можете реализовать инкапсуляцию, сделав само перечисление частным членом этого класса. Перечисление все еще должно быть полностью определено в точке объявления, то есть в определении класса. Однако это не большая проблема, чем объявление там частных функций-членов, и не худшее раскрытие внутренней части реализации, чем это.
Если вам нужна более глубокая степень сокрытия деталей реализации, вы можете разбить его на абстрактный интерфейс, состоящий только из чистых виртуальных функций, и конкретный, полностью скрытый класс, реализующий (наследующий) интерфейс. Создание экземпляров класса может выполняться фабрикой или статической функцией-членом интерфейса. Таким образом, даже настоящее имя класса, не говоря уже о его частных функциях, не будет раскрыто.
Поскольку перечисление может быть целым размером переменного размера (компилятор решает, какой размер имеет данное перечисление), указатель на перечисление также может иметь переменный размер, поскольку это целочисленный тип (на некоторых платформах символы имеют указатели другого размера. например).
Таким образом, компилятор не может даже позволить вам переадресовать перечисление и указать пользователю указатель на него, потому что даже там ему нужен размер перечисления.
[Мой ответ неверен, но я оставил его здесь, потому что комментарии полезны].
Прямое объявление перечислений нестандартно, поскольку не гарантируется, что указатели на разные типы перечислений будут одного и того же размера. Компилятору может потребоваться увидеть определение, чтобы знать, какие указатели размера могут использоваться с этим типом.
На практике, по крайней мере, во всех популярных компиляторах указатели на перечисления имеют постоянный размер. Прямое объявление перечислений предоставляется, например, как расширение языка Visual C++.
-1. Если бы ваше рассуждение было правильным, то же рассуждение означало бы, что прямые объявления типов классов нельзя использовать для создания указателей на эти типы, но они могут.
+1. Рассуждение верное. Конкретный случай - платформы, где sizeof (char *)> sizeof (int *). Оба могут быть базовыми типами для перечисления, в зависимости от диапазона. Классы не имеют базовых типов, поэтому аналогия неверна.
@MSalters: «Базовый тип» действительно является свойством перечисления, а не класса, но это не имеет значения - то же самое, что делается для классов мог, должно быть сделано для перечислений без возникновения противоречий. А именно, используя полноразмерные указатели (например, char *) и разрешая использование указателей перед определением.
@MSalters: Пример: "struct S {int x;};" Теперь sizeof (S *) должен будет равен размеру любого другого указателя на структуру, поскольку C++ позволяет объявить и использовать такой указатель до определения S ...
@MSalters: ... На платформе, где sizeof (char *)> sizeof (int *), использование такого "полноразмерного" указателя для этой конкретной структуры может быть неэффективным, но это значительно упрощает кодирование - и точно так же вещь может и должна быть сделана для перечислимых типов.
указатели на данные и указатели на функции могут быть разных размеров, но я почти уверен, что указатели данных должны передаваться туда и обратно (приводиться к другому типу указателя данных, а затем возвращаться к исходному, должны по-прежнему работать), что означает, что все указатели данных имеют одинаковый размер.
Хотя я удивлен, узнав, что sizeof (char *)> sizeof (int *) когда-либо возможно, sizeof (char *) == sizeof (int *) во всем, от 8088 до ARM и Itanium, поэтому глупо выдавать ошибку на все платформы для кода, который почти всегда однозначен. Что еще более важно, даже если (sizeof (char *)! = Sizeof (int *)) компилятору не нужно знать размер E или E * только для компиляции «enum E; void Foo (E e);». Единственное разумное требование состоит в том, что E необходимо определить до того, как вы сможете вызвать или определить Foo ().
Я бы сделал так:
[в общедоступном заголовке]
typedef unsigned long E;
void Foo(E e);
[во внутреннем заголовке]
enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
FORCE_32BIT = 0xFFFFFFFF };
Добавляя FORCE_32BIT, мы гарантируем, что Econtent компилируется в long, поэтому он взаимозаменяем с E.
Конечно, это означает, что (A) типы E и Econtent различаются, и (B) в системах LP64 sizeof (E) = 2 * sizeof (EContent). Тривиальное исправление: ULONG_MAX, к тому же легче читать.
Причина, по которой перечисление не может быть объявлено вперед, заключается в том, что, не зная значений, компилятор не может знать хранилище, необходимое для переменной перечисления. Компиляторам C++ разрешено указывать фактическое пространство для хранения в зависимости от размера, необходимого для хранения всех указанных значений. Если все, что видно, - это предварительное объявление, единица трансляции не может знать, какой размер памяти был выбран - это может быть char, int или что-то еще.
Из раздела 7.2.5 стандарта ISO C++:
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than
intunless the value of an enumerator cannot fit in anintorunsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value ofsizeof()applied to an enumeration type, an object of enumeration type, or an enumerator, is the value ofsizeof()applied to the underlying type.
Поскольку звонящий функции должен знать размеры параметров, чтобы правильно настроить стек вызовов, количество перечислений в списке перечисления должно быть известно до прототипа функции.
Обновлять:
В C++ 0X был предложен и принят синтаксис для прямого объявления перечислимых типов. Вы можете увидеть предложение на Прямое объявление перечислений (ред. 3)
Теперь, когда ваш ответ принят, не могли бы вы отредактировать свой ответ?
Обратите внимание, что намерение определения перечисления ограничивает его возможные значения. Форвардное декларирование превосходит это.
-1. Ваше рассуждение не может быть правильным - в противном случае, почему вам разрешено пересылать объявление "class C;" а затем объявить прототип функции, которая принимает или возвращает C, до полного определения C?
Я хочу сказать, что при объявлении прототипа функции компилятору не нужно знать размер параметра, независимо от того, является ли он классом или перечислением. Ясно, что здесь полезно указывать неполные типы классов, и это прекрасно работает - так почему бы не использовать неполные типы перечислений?
Если это так, можно ли указать размер хранилища в форвардном объявлении.
@j_random: вы не можете использовать класс до того, как он полностью определен - вы можете использовать только указатель или ссылку на этот класс, и это потому, что их размеры и способы операций не зависят от того, что это за класс.
@RnR: Конечно, вы не можете объявить переменную класса C или запросить sizeof (C) до определения C, но тот факт, что вы может объявляете указатели и ссылки на C до того, как он определен, ограничивает зависимости компиляции (меньше #includes = = быстрее). Должно быть возможно сделать то же самое с перечислениями.
Размер ссылки или указателя на объект класса устанавливается компилятором и не зависит от фактического размера объекта - это размер указателей и ссылок. Перечисление - это объект, и его размер необходим компилятору для доступа к правильному хранилищу.
Логически можно было бы объявлять указатели / ссылки на перечисления, если бы у нас были перечисления с прямым объявлением, точно так же, как мы можем делать с классами. Просто с указателями на перечисления не часто сталкиваешься :)
Я знаю, что это обсуждение закончилось давным-давно, но я должен присоединиться к @j_random_hacker здесь: проблема здесь не в указателе или ссылке на неполные типы, а в использовании неполных типов в объявлениях. Поскольку выполнение struct S; void foo(S s); является законным (обратите внимание, что foo только объявлен, а не определен), то нет причин, по которым мы также не могли бы сделать enum E; void foo(E e);. В обоих случаях размер не нужен.
Спасибо @Luc. Возможно, я был немного грубоват, было неожиданностью узнать, что struct S; void foo(S s); является законным C++, потому что, конечно, struct S; void foo(S s) {} не является (в этом случае должен быть определен S). Но похоже, что большинство людей не заметили различия между декларациями и определениями.
Я знаю, что это старое обсуждение, но я хочу еще раз подчеркнуть, что, несмотря на количество людей, проголосовавших за него, @RnR в значительной степени ошибочен - совершенно законно использовать класс до того, как он будет полностью определен в определенных ситуациях, включая в качестве параметров функций или возвращаемых значений из функций. Если вы мне не верите, попробуйте - объявите class A; и void foo(A);, затем предоставьте реализации для обоих и посмотрите, компилируется ли он.
@Tom - Я бы сказал, что объявление не является с помощью классом; тогда как разыменование или доступ к полям в классе есть. +1 для RnR;)
@nevelis - RnR сказал: «Вы можете использовать только указатель или ссылку на этот класс», что просто неверно, следовательно, неверно.
RnR, конечно, был прав, говоря, что вы можете использовать классы до того, как они были определены, если использование таково, что «их размеры и способы операций не зависят от того, что это за класс». Утверждение, что «указатель или ссылка» - не единственное такое использование, является придиркой, и отрицательные голоса этого ответа являются фиктивными, потому что логика верна в вопросе OP. И ответ правильный: перечисления могут быть объявлены вперед в C++ 11, потому что базовый тип и, следовательно, размер известны.
Это не придирки - вся дискуссия идет об использовании опережающих определений классов в прототипе функции. j_random_hacker сказал, что вы можете это сделать, RnR сказал, что вы не можете - он ошибается.
Я не понимаю аргумент размера, который часто используется. Размер класса неизвестен до тех пор, пока он не будет полностью определен, но его можно объявить вперед, а затем использовать не только как указатель и ссылку, но также как параметр, тип возвращаемого значения и при передаче в качестве параметра другой функции, см. Herb gotw.ca/publications/mill04.htm Саттера встроенный std :: ostream & operator << (std :: ostream & os, const X & x) {return x.print (os); } пример и объяснение.
Я выбрал плохой пример кода. Как говорит j_random_hacker, вы можете объявить прототип функции, которая принимает или возвращает класс (даже по значению), не зная его размера. Конечно, «вызывающая функция должна знать размеры параметров для правильной настройки стека вызовов», но вызывающая сторона может включить определение класса, чтобы получить его размер. Почему это не относится к перечислению?
@RnR, я не согласен с вашим комментарием. В примере OP аргумент используется как параметр объявления функции. Если бы это был заранее объявленный класс в списке параметров, все бы просто работало. Таким образом, компилятору не нужно знать размер параметра при объявлении функции.
@JimBalter, я на той же стороне, что и j_random_hacker, LucTouraille и Том, что, хотя RnR получает большое количество голосов, это очень неправильно. Те, кто проголосовали за него, не до конца понимают вопрос.
Вау ... 5 лет :) Что забавно, я также хотел бы иметь возможность переслать объявление перечисления;) Что касается разочарования из-за невозможности сделать это и использовать их в декларация чего-то - мы также не можем вперед объявить typedef и использовать его в объявлении, верно? (typedef A;) Это плохо и делает нас непродуктивными? Я согласен с тем, что мой общий комментарий был неправильным, ЕСЛИ мы рассматриваем использование класса в объявлении как с помощью этого класса, НО я согласен с Джимом в том, что считать его «использованием» немного натянуто, и я могу подтвердить, что это не то, что я имел в виду. Настоящий ответ должен был быть «потому что ты не можешь»;)
Я бы посоветовал хулителям @KJAWolf удалить свои комментарии. Вы уже 7 лет публично ставите себя в неловкое положение. Обновленный стандарт C++ решает эту конкретную проблему с помощью средства, которое точно подтверждает то, что заявил Вольф. И спасибо, Вольф, за размещение средства.
@natersoz: Как стыдно? Стандарт не ошибочен; люди просто говорят, что требования стандарта более строгие, чем это действительно необходимо. И действительно, похоже. Если вы считаете иначе, объясните, пожалуйста, людям Почему, какой размер действительно требуется (своими словами). Я подозреваю, что ты не сможешь.
Мое решение вашей проблемы:
1 - используйте int вместо перечислений: объявите ваши int в анонимном пространстве имен в вашем файле CPP (не в заголовке):
namespace
{
const int FUNCTIONALITY_NORMAL = 0 ;
const int FUNCTIONALITY_RESTRICTED = 1 ;
const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}
Поскольку ваши методы являются частными, никто не будет связываться с данными. Вы даже можете пойти дальше, чтобы проверить, отправляет ли кто-то неверные данные:
namespace
{
const int FUNCTIONALITY_begin = 0 ;
const int FUNCTIONALITY_NORMAL = 0 ;
const int FUNCTIONALITY_RESTRICTED = 1 ;
const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
const int FUNCTIONALITY_end = 3 ;
bool isFunctionalityCorrect(int i)
{
return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
}
}
2: создать полный класс с ограниченными экземплярами констант, как это сделано в Java. Вперед объявите класс, а затем определите его в файле CPP и создайте только значения, подобные перечислению. Я сделал что-то подобное на C++, и результат оказался не таким удовлетворительным, как хотелось бы, поскольку требовался некоторый код для имитации перечисления (конструкция копирования, оператор = и т. д.).
3: Как предлагалось ранее, используйте объявленное частным образом перечисление. Несмотря на то, что пользователь увидит его полное определение, он не сможет ни использовать его, ни использовать частные методы. Таким образом, вы обычно сможете изменить перечисление и содержимое существующих методов без необходимости перекомпиляции кода с использованием вашего класса.
Я предполагаю, что это решение 3 или 1.
Если вы действительно не хотите, чтобы ваше перечисление отображалось в заголовочном файле и, убедитесь, что оно используется только частными методами, тогда одним из решений может быть принцип PIMPL.
Это метод, позволяющий скрыть внутреннее содержимое класса в заголовках, просто объявив:
class A
{
public:
...
private:
void* pImpl;
};
Затем в вашем файле реализации (.cpp) вы объявляете класс, который будет представлять внутренние компоненты.
class AImpl
{
public:
AImpl(A* pThis): m_pThis(pThis) {}
... all private methods here ...
private:
A* m_pThis;
};
Вы должны динамически создать реализацию в конструкторе класса и удалить ее в деструкторе, а при реализации общедоступного метода вы должны использовать:
((AImpl*)pImpl)->PrivateMethod();
У использования PIMPL есть свои плюсы. Во-первых, он отделяет заголовок вашего класса от его реализации, и нет необходимости перекомпилировать другие классы при изменении реализации одного класса. Во-вторых, это ускоряет время компиляции, потому что ваши заголовки очень просты.
Но использовать это неудобно, поэтому вы действительно должны спросить себя, действительно ли просто объявление вашего перечисления как закрытого в заголовке является такой большой проблемой.
struct AImpl; struct A {private: AImpl * pImpl; };
Вы определяете перечисление, чтобы ограничить возможные значения элементов типа ограниченным набором. Это ограничение должно применяться во время компиляции.
При форварде объявление того факта, что вы будете использовать «ограниченный набор» позже, не добавляет никакой ценности: последующий код должен знать возможные значения, чтобы извлечь из этого пользу.
Хотя компилятор является заботится о размере перечисляемого типа, намерение перечисления теряется при пересылке его объявления.
Нет, последующему коду не обязательно знать значения, чтобы это было полезно - в частности, если последующий код является просто прототипом функции, принимающим или возвращающим параметры перечисления, размер типа не важен. Использование прямого объявления здесь может удалить зависимости сборки, ускоряя компиляцию.
Ты прав. Цель - подчиняться не ценностям, а типу. Решено с помощью типов Enum 0x.
Есть некоторое несогласие с этим (вроде как), так что вот некоторые важные моменты из стандарта. Исследования показывают, что стандарт на самом деле не определяет прямое объявление и не указывает явно, что перечисления могут или не могут быть объявлены вперед.
Во-первых, из dcl.enum, раздел 7.2:
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value of sizeof() applied to an enumeration type, an object of enumeration type, or an enumerator, is the value of sizeof() applied to the underlying type.
Таким образом, базовый тип перечисления определяется реализацией с одним незначительным ограничением.
Затем мы переходим к разделу «неполные типы» (3.9), который примерно так же близок к любому стандарту для форвардных объявлений:
A class that has been declared but not defined, or an array of unknown size or of incomplete element type, is an incompletely-defined object type.
A class type (such as "class X") might be incomplete at one point in a translation unit and complete later on; the type "class X" is the same type at both points. The declared type of an array object might be an array of incomplete class type and therefore incomplete; if the class type is completed later on in the translation unit, the array type becomes complete; the array type at those two points is the same type. The declared type of an array object might be an array of unknown size and therefore be incomplete at one point in a translation unit and complete later on; the array types at those two points ("array of unknown bound of T" and "array of N T") are different types. The type of a pointer to array of unknown size, or of a type defined by a typedef declaration to be an array of unknown size, cannot be completed.
Итак, в стандарте в значительной степени изложены типы, которые могут быть объявлены вперед. Enum там не было, поэтому авторы компиляторов обычно считают форвардное объявление запрещенным стандартом из-за переменного размера его базового типа.
Это тоже имеет смысл. На перечисления обычно ссылаются в ситуациях по значению, и компилятору действительно необходимо знать размер хранилища в таких ситуациях. Поскольку размер хранилища определяется реализацией, многие компиляторы могут просто использовать 32-битные значения для базового типа каждого перечисления, после чего становится возможным пересылать их объявление.
Интересным экспериментом может быть попытка объявить перечисление в Visual Studio, а затем заставить его использовать базовый тип, больший, чем sizeof (int), как описано выше, чтобы увидеть, что произойдет.
обратите внимание, что он явно запрещает "enum foo;" в 7.1.5.3/1 (но, как и все остальное, если компилятор предупреждает, он, конечно, может компилировать такой код)
Спасибо, что указали на это, это действительно эзотерический абзац, и мне может потребоваться неделя, чтобы разобрать его. Но хорошо знать, что он там есть.
не беспокойтесь. некоторые стандартные абзацы действительно странные :) ну, детализированный спецификатор типа - это то, где вы указываете тип, но также указываете что-то еще, чтобы сделать его однозначным. например, «struct X» вместо «X» или «enum Y» вместо «Y». Вам нужно, чтобы он утверждал, что что-то действительно является типом.
так что вы можете использовать его так: "class X * foo;" если X еще не объявлен вперед. или "typename X :: foo" в шаблоне для устранения неоднозначности. или "class link obj;" если есть функция «ссылка» в той же области видимости, которая затеняет класс с тем же именем.
в 3.4.4 говорится, что они используются, если какое-то имя, не являющееся типом, скрывает имя типа. вот где они чаще всего используются, кроме прямого объявления типа "class X;" (здесь это единственный состав декларации). здесь о них говорится в не-шаблонах. однако в 14.6 / 3 перечислено их использование в шаблонах.
Я просто отмечаю, что на самом деле причина является в том, что размер перечисления еще не известен после прямого объявления. Что ж, вы используете прямое объявление структуры, чтобы иметь возможность передавать указатель или ссылаться на объект из места, на которое ссылается само определение структуры прямого объявления.
Прямое объявление перечисления было бы не слишком полезным, потому что хотелось бы иметь возможность передавать перечисление по значению. У вас не могло быть даже указателя на него, потому что мне недавно сказали, что некоторые платформы используют указатели другого размера для символ, чем для int или длинный. Так что все зависит от содержимого перечисления.
Текущий стандарт C++ явно запрещает делать что-то вроде
enum X;
(в 7.1.5.3/1). Но следующий стандарт C++, который должен выйти в следующем году, допускает следующее, что убедило меня в том, что проблема имеет на самом деле связана с базовым типом:
enum X : int;
Это называется «непрозрачным» объявлением перечисления. Вы даже можете использовать X по стоимости в следующем коде. И его счетчики могут быть позже определены при более позднем повторном объявлении перечисления. См. 7.2 в текущем рабочем проекте.
Для VC++ вот тест на предварительное объявление и указание базового типа:
typedef int myint;
enum T ;
void foo(T * tp )
{
* tp = (T)0x12345678;
}
enum T : char
{
A
};
Но я получил предупреждение для /W4 (/W3 не несет этого предупреждения)
warning C4480: nonstandard extension used: specifying underlying type for enum 'T'
?foo@@YAXPAW4T@@@Z PROC ; foo
; File e:\work\c_cpp\cpp_snippet.cpp
; Line 13
push ebp
mov ebp, esp
; Line 14
mov eax, DWORD PTR _tp$[ebp]
mov DWORD PTR [eax], 305419896 ; 12345678H
; Line 15
pop ebp
ret 0
?foo@@YAXPAW4T@@@Z ENDP ; foo
Приведенный выше ассемблерный код извлекается непосредственно из /Fatest.asm, а не мое личное предположение.
Вы видите
mov DWORD PTR[eax], 305419896 ; 12345678H
линия?
следующий фрагмент кода доказывает это:
int main(int argc, char *argv)
{
union {
char ca[4];
T t;
}a;
a.ca[0] = a.ca[1] = a.[ca[2] = a.ca[3] = 1;
foo( &a.t) ;
printf("%#x, %#x, %#x, %#x\n", a.ca[0], a.ca[1], a.ca[2], a.ca[3] );
return 0;
}
Результат:
0x78, 0x56, 0x34, 0x12
Приведенная выше ключевая инструкция становится:
mov BYTE PTR [eax], 120; 00000078H
Конечный результат:
0x78, 0x1, 0x1, 0x1
Обратите внимание, что значение не перезаписывается.
Поэтому использование прямого объявления enum в VC++ считается вредным.
Кстати, что неудивительно, синтаксис объявления базового типа такой же, как и в C#. На практике я обнаружил, что стоит сэкономить три байта, указав базовый тип как char при разговоре со встроенной системой, которая ограничена памятью.
В своих проектах я использовал технику Перечисление, привязанное к пространству имен для работы с enum из устаревших и сторонних компонентов. Вот пример:
namespace type
{
class legacy_type;
typedef const legacy_type& type;
}
// May be defined here or pulled in via #include.
namespace legacy
{
enum evil { x , y, z };
}
namespace type
{
using legacy::evil;
class legacy_type
{
public:
legacy_type(evil e)
: e_(e)
{}
operator evil() const
{
return e_;
}
private:
evil e_;
};
}
#include "forward.h"
class foo
{
public:
void f(type::type t);
};
#include "foo.h"
#include <iostream>
#include "enum.h"
void foo::f(type::type t)
{
switch (t)
{
case legacy::x:
std::cout << "x" << std::endl;
break;
case legacy::y:
std::cout << "y" << std::endl;
break;
case legacy::z:
std::cout << "z" << std::endl;
break;
default:
std::cout << "default" << std::endl;
}
}
#include "foo.h"
#include "enum.h"
int main()
{
foo fu;
fu.f(legacy::x);
return 0;
}
Обратите внимание, что заголовок foo.h не должен ничего знать о legacy::evil. Только файлы, которые используют устаревший тип legacy::evil (здесь: main.cc), должны включать enum.h.
Прямое объявление перечислений возможно, начиная с C++ 11. Ранее причина, по которой типы перечислений не могли быть объявлены вперед, заключалась в том, что размер перечисления зависел от его содержимого. Пока размер перечисления определяется приложением, его можно объявить вперед:
enum Enum1; // Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int; // Legal in C++0x.
enum class Enum3; // Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; // Legal C++0x.
enum Enum2 : unsigned short; // Illegal in C++0x, because Enum2 was previously declared with a different type.
Поддерживается ли эта функция компилятором? В GCC 4.5 этого нет :(
@rubenvb Как и Visual C++ 11 (2012) blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
Я искал enum32_t и с вашим ответом enum XXX: uint32_t {a, b, c};
Я думал, что перечисления с областью видимости (класс перечисления) были реализованы в С ++ 11? Если да, то насколько они законны в C++ 0X?
Что, если перечисление находится в пространстве имен какого-то класса? enum class MyClass::Enum4: unsigned int;, похоже, не работает.
Видимо ты не может пересылать объявление вложенных типов. Означает ли это, что перечисления всегда следует делать глобальными, если они используются несколькими классами?
C++ 0x был рабочим названием C++ 11, @Terrabits, до того, как он был официально стандартизирован. Логика заключается в том, что если известно (или весьма вероятно), что функция будет включена в обновленный стандарт, то при использовании этой функции до официального выпуска стандарта обычно используется рабочее имя. (Например, компиляторы, которые поддерживали функции C++ 11 до официальной стандартизации в 2011 году, имели поддержку C++ 0x, компиляторы, которые поддерживали функции C++ 17 до официальной стандартизации, имели поддержку C++ 1z, а компиляторы, поддерживающие функции C++ 20 прямо сейчас (2019) есть поддержка C++ 2a.)
Пожалуйста, @Terrabits. Поначалу это может быть немного сложно, особенно в случае 0x == beta(11), но это помогает знать об этом, поскольку многие компиляторы, как правило, используют рабочие имена для флагов, чтобы включить будущие стандартные функции для использования.
Прямое объявление вещей в C++ очень полезно, потому что это значительно ускоряет время компиляции. Вы можете переадресовать объявление нескольких вещей на C++, включая struct, class, function и т. д.
Но можете ли вы переадресовать объявление enum на C++?
Нет, не можешь.
Но почему бы не допустить этого? Если бы это было разрешено, вы могли бы определить свой тип enum в файле заголовка и значения enum в исходном файле. Похоже, это должно быть разрешено, не так ли?
Неправильный.
В C++ нет типа по умолчанию для enum, как в C# (int). В C++ ваш тип enum будет определен компилятором как любой тип, который будет соответствовать диапазону значений, который у вас есть для вашего enum.
Что это обозначает?
Это означает, что базовый тип вашего enum не может быть полностью определен, пока вы не определите все значения enum. Это означает, что вы не можете разделить декларацию и определение вашего enum. И поэтому вы не можете пересылать объявление enum на C++.
Стандарт ISO C++ S7.2.5:
The underlying type of an enumeration is an integral type that can represent all the enumerator values defined in the enumeration. It is implementation-defined which integral type is used as the underlying type for an enumeration except that the underlying type shall not be larger than
intunless the value of an enumerator cannot fit in anintorunsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0. The value ofsizeof()applied to an enumeration type, an object of enumeration type, or an enumerator, is the value ofsizeof()applied to the underlying type.
Вы можете определить размер перечислимого типа в C++ с помощью оператора sizeof. Размер перечисляемого типа - это размер его базового типа. Таким образом, вы можете угадать, какой тип компилятор использует для вашего enum.
Что делать, если вы явно укажете тип вашего enum следующим образом:
enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);
Можете ли вы тогда заявить свой enum?
Нет. Но почему бы и нет?
Указание типа enum на самом деле не является частью текущего стандарта C++. Это расширение VC++. Однако он будет частью C++ 0x.
Этот ответ устарел на несколько лет.
Время делает всех нас дураками. Ваш комментарий устарел на несколько лет; ответ десятилетие!
Вы можете обернуть перечисление в структуру, добавив некоторые конструкторы и преобразования типов, и вместо этого объявить структуру вперед.
#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
enum e { VALUES }; \
explicit NAME(TYPE v) : val(v) {} \
NAME(e v) : val(v) {} \
operator e() const { return e(val); } \
private:\
TYPE val; \
}
Кажется, это работает: http://ideone.com/TYtP2
Вы можете напрямую объявить перечисление в C++ 11, если вы одновременно объявляете его тип хранилища. Синтаксис выглядит так:
enum E : short;
void foo(E e);
....
enum E : short
{
VALUE_1,
VALUE_2,
....
}
Фактически, если функция никогда не ссылается на значения перечисления, вам вообще не нужно полное объявление на этом этапе.
Это поддерживается G ++ 4.6 и новее (-std=c++0x или -std=c++11 в более поздних версиях). Visual C++ 2013 поддерживает это; в более ранних версиях у него была какая-то нестандартная поддержка, которую я еще не понял - я нашел некоторые предположения, что простое форвардное объявление является законным, но ваш опыт может отличаться.
+1, потому что это единственный ответ, в котором упоминается, что вам нужно объявить тип в своем объявлении, а также в своем определении.
Я считаю, что частичная поддержка в раннем MSVC была перенесена из C++ / CLI enum class в качестве расширения C++ (до другого enum class в C++ 11), по крайней мере, если я правильно помню. Компилятор позволил вам указать базовый тип перечисления, но не поддерживал enum class или перечисления, объявленные вперед, и предупредил вас, что квалификация перечислителя с областью действия перечисления является нестандартным расширением. Я помню, что он работал примерно так же, как указание базового типа в C++ 11, за исключением того, что это раздражало, потому что вам приходилось подавлять предупреждение.
Всем, кто сталкивается с этим для iOS / Mac / Xcode,
Если вы столкнулись с этим при интеграции заголовков C / C++ в XCode с Objective-C, просто измените расширение вашего файла с .mm на .m
Какое объяснение? Почему это работает?
Прошло столько лет, и StackOverflow каким-то образом заманил меня обратно;) В качестве посмертного предложения - просто не делай этогоособенно в сценарии, который вы описываете. Я бы предпочел определить абстрактный интерфейс и предоставить его пользователям и сохранить определение перечисления и все другие детали реализации с внутренней реализацией, которую никто другой не видит на моей стороне, что позволяет мне делать что угодно в любое время и иметь полный контроль над тем, когда пользователи видят что-либо.