Вперед, объявляя перечисление в C ++

265

Я пытаюсь сделать что-то вроде следующего:

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 - это не то, о чем я хочу, чтобы мои пользователи знали.

Итак, я хотел переслать объявление enum, чтобы я мог поместить приватные методы в файл заголовка, объявить enum внутри cpp и распространить файл и заголовок встроенной библиотеки людям.

Что касается компилятора - это GCC.

szevvy
источник
Столько лет в этом и каким-то образом StackOverflow заманил меня обратно;) В качестве посмертного предложения - просто не делайте этого, особенно в сценарии, который вы описываете. Я бы предпочел определить абстрактный интерфейс и показать его всем пользователям, а также сохранить определение enum и все другие подробности реализации с внутренней реализацией, которую никто не видит на моей стороне, позволяющей мне делать что угодно когда угодно и иметь полный контроль над тем, когда пользователи видят что-нибудь.
RnR
Если вы прочитаете принятый ответ, это вполне возможно, начиная с C ++ 11.
fuzzyTew

Ответы:

217

Причина, по которой перечисление не может быть объявлено заблаговременно, заключается в том, что без знания значений компилятор не может узнать, какое хранилище требуется для переменной перечисления. Компиляторам C ++ разрешено указывать фактическое пространство хранения на основе размера, необходимого для хранения всех указанных значений. Если все, что видно, - это предварительное объявление, модуль перевода не может знать, какой объем хранилища будет выбран - это может быть char, int или что-то еще.


Из раздела 7.2.5 стандарта ISO C ++:

Базовый типом из перечисления является составным типом , который может представлять все значения перечислителя , определенные в перечислении. Определяется реализацией, какой интегральный тип используется в качестве базового типа для перечисления, за исключением того, что базовый тип не должен быть больше, чем intесли значение перечислителя не может поместиться в intили unsigned int. Если список перечислителя пуст, базовый тип выглядит так, как если бы перечисление имело единственный перечислитель со значением 0. Значение, sizeof()примененное к типу перечисления, объекту типа перечисления или перечислителю, является значением, sizeof()примененным к базовый тип.

Поскольку вызывающая функция должна знать размеры параметров для правильной настройки стека вызовов, число перечислений в списке перечислений должно быть известно до прототипа функции.

Обновление: в C ++ 0X был предложен и принят синтаксис для объявляющих заранее типов enum. Вы можете увидеть это предложение по адресу http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf.

KJAWolf
источник
29
-1. Ваши рассуждения не могут быть правильными - в противном случае, почему вам разрешено заранее объявить "класс C"? а затем объявить прототип функции, которая принимает или возвращает C, прежде чем полностью определить C?
j_random_hacker
112
@j_random: Вы не можете использовать класс, пока он полностью не определен - вы можете использовать только указатель или ссылку на этот класс, и это потому, что их размеры и способы работы не зависят от того, что это за класс.
RnR
27
Размер ссылки или указателя на объект класса устанавливается компилятором и не зависит от фактического размера объекта - это размер указателей и ссылок. Перечисление является объектом, и его размер необходим компилятору для доступа к правильному хранилищу.
KJAWolf
17
Логически было бы возможно объявить указатели / ссылки на перечисления, если бы у нас были объявляемые вперед перечисления, точно так же, как мы можем сделать с классами. Просто вы не часто имеете дело с указателями на перечисления :)
Павел Минаев
20
Я знаю, что это обсуждение закончилось давным-давно, но я должен выстроиться в соответствие с @j_random_hacker здесь: проблема здесь не в указателе или ссылке на неполные типы, а в использовании неполных типов в объявлениях. Поскольку это допустимо struct S; void foo(S s);(обратите внимание, что fooэто только объявлено, но не определено), то нет никаких причин, почему мы не могли бы сделать enum E; void foo(E e);это также. В обоих случаях размер не нужен.
Люк Турай
201

Предварительное объявление перечислений возможно начиная с 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.
user119017
источник
1
Есть ли какая-либо поддержка компилятора для этой функции? GCC 4.5, похоже, не имеет его :(
rubenvb
4
@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};
фантастика
Я думал, что enum с областью видимости (enum class) были реализованы в C ++ 11? Если так, как они тогда законны в C ++ 0X?
Terrabits
1
C ++ 0x было рабочим названием для C ++ 11, @Terrabits, до того как оно было официально стандартизировано. Логика заключается в том, что если известно, что функция (или весьма вероятно) включена в обновленный стандарт, то использование этой функции до официального выпуска стандарта имеет тенденцию использовать рабочее имя. (Например, компиляторы, которые поддерживали функции C ++ 11 до официальной стандартизации в 2011 году, имели поддержку C ++ 0x, компиляторы, которые поддерживали функции C ++ 17 до официальной стандартизации, имели поддержку C ++ 1z, и компиляторы, которые поддерживают функции C ++ 20 Прямо сейчас (2019) есть поддержка C ++ 2a.)
Джастин Тайм - Восстановить Монику
79

Я добавляю актуальный ответ здесь, учитывая последние события.

Вы можете заранее объявить перечисление в 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 поддерживает это; в более ранних версиях у него была какая-то нестандартная поддержка, которую я еще не понял - я обнаружил некоторые предположения, что простое предварительное объявление является законным, но YMMV.

Том
источник
4
+1, потому что это единственный ответ, в котором упоминается, что вам нужно объявить тип в вашем объявлении, а также ваше определение.
Турони
Я считаю, что частичная поддержка в ранних версиях MSVC была перенесена из C ++ / CLI enum classкак расширение C ++ (до C ++ 11 отличалось enum class), по крайней мере, если я правильно помню. Компилятор позволял вам указывать базовый тип перечисления, но не поддерживал enum classперечисления и не объявлял их заранее и предупреждал, что квалификация перечислителя с областью действия перечисления является нестандартным расширением. Я помню, что он работает примерно так же, как указание базового типа в C ++ 11, за исключением более раздражающего, потому что вам пришлось подавить предупреждение.
Джастин Тайм - Восстановите Монику
30

Прямое объявление вещей в 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:

Базовый тип перечисления является целочисленным типом, который может представлять все значения перечислителя, определенные в перечислении. Определяется реализацией, какой интегральный тип используется в качестве базового типа для перечисления, за исключением того, что базовый тип не должен быть больше, чем intесли значение перечислителя не может поместиться в intили unsigned int. Если список перечислителя пуст, базовый тип выглядит так, как если бы перечисление имело один перечислитель со значением 0. Значение, sizeof()примененное к типу перечисления, объекту типа перечисления или перечислителю, является значением, sizeof()примененным к базовый тип.

Вы можете определить размер перечисляемого типа в C ++ с помощью sizeofоператора. Размер перечисляемого типа - это размер его базового типа. Таким образом, вы можете угадать, какой тип ваш компилятор использует для вашего enum.

Что, если вы укажете тип вашего enumявно так:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

Можете ли вы затем объявить свое enum?

Нет. Но почему нет?

Указание типа an на enumсамом деле не является частью текущего стандарта C ++. Это расширение VC ++. Это будет частью C ++ 0x.

Источник

Брайан Р. Бонди
источник
15
Этот ответ уже несколько лет устарел.
Том
Время делает нас всех дураками. Ваш комментарий устарел уже на несколько лет; ответ десятилетие!
pjcard
14

[Мой ответ неверный, но я оставил его здесь, потому что комментарии полезны].

Прямое объявление перечислений не является стандартным, поскольку указатели на разные типы перечислений не гарантируют одинаковый размер. Компилятору может понадобиться увидеть определение, чтобы узнать, какие указатели размера могут использоваться с этим типом.

На практике, по крайней мере, на всех популярных компиляторах указатели на перечисления имеют одинаковый размер. Прямое объявление перечислений предоставляется, например, как расширение языка Visual C ++.

Джеймс Хопкин
источник
2
-1. Если ваше рассуждение было правильным, то такое же рассуждение подразумевало бы, что предварительные объявления типов классов не могут использоваться для создания указателей на эти типы - но они могут.
j_random_hacker
6
+1. Рассуждения верны. Конкретный случай - это платформы, где sizeof (char *)> sizeof (int *). Оба могут быть базовыми типами для перечисления, в зависимости от диапазона. Классы не имеют базовых типов, поэтому аналогия неверна.
MSalters
3
@MSalters: Пример: "struct S {int x;};" Теперь sizeof (S *) должен быть равен размеру любого другого указателя на структуру, поскольку C ++ позволяет объявлять и использовать такой указатель до определения S ...
j_random_hacker,
1
@MSalters: ... На платформе, где sizeof (char *)> sizeof (int *), использование такого полноразмерного указателя для этой конкретной структуры может быть неэффективным, но это значительно упрощает кодирование - и в точности то же самое это можно и нужно сделать для перечислимых типов.
j_random_hacker
4
указатели на данные и указатели на функции могут быть разных размеров, но я вполне уверен, что указатели на данные должны идти в обоих направлениях (приведение к другому типу указателя данных, а затем обратно к оригиналу, все еще должно работать), что подразумевает, что все указатели данных имеют одинаковый размер.
Бен Фойгт
7

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

Если единственное использование вашего enum - это закрытые функции-члены, вы можете реализовать инкапсуляцию, если сам enum будет закрытым членом этого класса. Перечисление все еще должно быть полностью определено в точке объявления, то есть в пределах определения класса. Тем не менее, это не большая проблема, так как объявление частных функций-членов там, и не хуже демонстрации внутренних функций реализации, чем это.

Если вам нужна более глубокая степень скрытия для деталей вашей реализации, вы можете разбить его на абстрактный интерфейс, состоящий только из чисто виртуальных функций и конкретного, полностью скрытого, класса, реализующего (наследующего) интерфейс. Создание экземпляров класса может быть обработано фабрикой или статической функцией-членом интерфейса. Таким образом, даже настоящее имя класса, не говоря уже о его частных функциях, не будет открыто.

Алексей Фельгендлер
источник
5

Просто отметив, что причина на самом деле в том, что размер перечисления еще не известен после предварительного объявления. Ну, вы используете прямое объявление структуры, чтобы иметь возможность передавать указатель или ссылаться на объект из того места, на которое ссылаются в самом объявлении прямого определения структуры.

Объявление перечисления не было бы слишком полезным, потому что хотелось бы иметь возможность передавать перечисление по значению. Вы даже не могли иметь указатель на него, потому что мне недавно сказали, что некоторые платформы используют указатели разного размера для char, чем для int или long. Так что все зависит от содержания перечисления.

Текущий стандарт C ++ явно запрещает делать что-то вроде

enum X;

7.1.5.3/1). Но на следующем стандарт C ++ из - за следующий год позволяет следующее, что убедило меня проблему на самом деле имеет дело с базовым типом:

enum X : int;

Это известно как "непрозрачная" декларация перечисления. Вы даже можете использовать X по значению в следующем коде. И его перечислители могут быть позже определены в последующем повторном объявлении перечисления. Смотрите 7.2в текущем рабочем проекте.

Йоханнес Шауб - Литб
источник
4

Я бы сделал это так:

[в публичном заголовке]

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.

Лори Приветствия
источник
1
Конечно, это означает, что (A) типы E и Econtent различаются, и (B) в системах LP64 sizeof (E) = 2 * sizeof (EContent). Тривиальное исправление: ULONG_MAX, также легче читается.
MSalters
2

Если вы действительно не хотите, чтобы ваш enum появлялся в вашем заголовочном файле и гарантировал, что он используется только частными методами, тогда одним из решений может быть использование принципа 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, во-первых, он отделяет заголовок вашего класса от его реализации, нет необходимости перекомпилировать другие классы при изменении реализации одного класса. Другое - это ускоряет ваше время компиляции, потому что ваши заголовки очень просты.

Но это неудобно в использовании, поэтому вы должны спросить себя, не является ли проблема просто объявлением вашего enum как private в заголовке.

Винсент Роберт
источник
3
struct AImpl; struct A {private: AImpl * pImpl; };
2

Вы можете обернуть перечисление в структуру, добавив некоторые конструкторы и преобразования типов, и вместо этого объявить структуру вперед.

#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

Лешек Свирски
источник
1

Кажется, это не может быть заранее объявлено в GCC!

Интересная дискуссия здесь

Пракаш
источник
1

Есть некоторое несогласие, так как это столкнулось (вроде), так что вот некоторые важные моменты из стандарта. Исследования показывают, что стандарт на самом деле не определяет предварительную декларацию, и при этом он явно не заявляет, что перечисления могут или не могут быть объявлены форвард.

Во-первых, из dcl.enum, раздел 7.2:

Базовый тип перечисления является целочисленным типом, который может представлять все значения перечислителя, определенные в перечислении. Определяется реализацией, какой целочисленный тип используется в качестве базового типа для перечисления, за исключением того, что базовый тип не должен быть больше, чем int, если только значение перечислителя не может поместиться в int или unsigned int. Если список-перечислитель пуст, базовый тип выглядит так, как если бы перечисление имело единственный перечислитель со значением 0. Значение sizeof (), примененное к типу перечисления, объекту типа перечисления или перечислителю, является значением sizeof () применяется к базовому типу.

Таким образом, базовый тип перечисления определяется реализацией с одним небольшим ограничением.

Далее мы перейдем к разделу «Неполные типы» (3.9), который примерно так же близок, как мы подошли к любому стандарту на предварительные объявления:

Класс, который был объявлен, но не определен, или массив неизвестного размера или с неполным типом элемента, является не полностью определенным типом объекта.

Тип класса (такой как «класс X») может быть неполным в одной точке в единице перевода и завершаться позже; тип "класс X" является одним и тем же типом в обеих точках. Объявленный тип объекта массива может быть массивом неполного типа класса и, следовательно, неполным; если тип класса завершается позже в модуле перевода, тип массива становится завершенным; тип массива в этих двух точках одинакового типа. Объявленный тип объекта массива может быть массивом неизвестного размера и, следовательно, может быть неполным в одной точке единицы преобразования и завершаться позже; типы массивов в этих двух точках («массив неизвестной границы T» и «массив N T») - это разные типы. Тип указателя на массив неизвестного размера или тип, определенный объявлением typedef как массив неизвестного размера,

Таким образом, стандарт в значительной степени изложил типы, которые могут быть объявлены заранее. Enum там не было, поэтому авторы компилятора обычно считают объявление вперед запрещенным стандартом из-за переменного размера его базового типа.

Это тоже имеет смысл. На перечисления обычно ссылаются в ситуациях побочного значения, и компилятору действительно нужно знать размер хранилища в этих ситуациях. Поскольку размер хранилища определяется реализацией, многие компиляторы могут просто использовать 32-битные значения для базового типа каждого перечисления, после чего становится возможным их прямое объявление. Интересным экспериментом может быть попытка объявить enum в визуальной студии, а затем заставить его использовать базовый тип, больший чем sizeof (int), как объяснено выше, чтобы увидеть, что происходит.

Дэн Олсон
источник
обратите внимание, что он явно запрещает "enum foo;" в 7.1.5.3/1 (но, как и во всем, пока компилятор предупреждает, он, конечно, может скомпилировать такой код)
Йоханнес Шауб - litb
Спасибо за указание на это, это действительно эзотерический абзац, и мне может потребоваться неделя, чтобы разобрать его. Но приятно знать, что это там.
Дэн Олсон
не беспокойтесь. некоторые стандартные абзацы действительно странные :) хорошо, разработанный спецификатор типа - это то, где вы указываете тип, но также указываете что-то большее, чтобы сделать его однозначным например, «struct X» вместо «X» или «enum Y» вместо «Y». Вам нужно, чтобы это утверждало, что это действительно тип.
Йоханнес Шауб -
так что вы можете использовать его так: "class X * foo;" если X еще не был объявлен вперед или "typename X :: foo" в шаблоне для устранения неоднозначности. или "ссылка на класс obj;" если есть функция «ссылка» в той же области видимости, которая будет скрывать класс с тем же именем.
Йоханнес Шауб -
в 3.4.4 говорится, что они используются, если какое-то нетипичное имя скрывает имя типа. именно там они чаще всего используются, за исключением объявления «вперед», как «класс X»; (здесь это единственная составляющая декларации). здесь говорится о них не в шаблонах. однако 14.6 / 3 перечисляет их использование в шаблонах.
Йоханнес Шауб -
1

Для VC вот тест на предварительное объявление и определение базового типа:

  1. следующий код скомпилирован нормально
    typedef int myint;
    enum T;
    void foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }
    enum T: char
    {
        
    };

Но получил предупреждение для / W4 (/ W3 не несет это предупреждение)

предупреждение C4480: используется нестандартное расширение: указание базового типа для перечисления 'T'

  1. VC (32-разрядный оптимизирующий компилятор C / C ++ версии 15.00.30729.01 для 80x86) выглядит некорректно в приведенном выше случае:

    • при просмотре enum T; VC предполагает, что тип перечисления T использует 4 байта по умолчанию int как базовый тип, поэтому сгенерированный код сборки:
    ? foo @@ YAXPAW4T @@@ Z PROC; Foo
    ; Файл e: \ work \ c_cpp \ cpp_snippet.cpp
    ; Линия 13
        толчок
        мов ебп, эсп
    ; Линия 14
        mov eax, DWORD PTR _tp $ [ebp]
        mov DWORD PTR [eax], 305419896; 12345678H
    ; Строка 15
        поп ебп
        рет 0
    ? foo @@ YAXPAW4T @@@ Z ENDP; Foo

Приведенный выше код сборки извлечен непосредственно из /Fatest.asm, а не мое личное предположение. Вы видите mov DWORD PTR [eax], 305419896; 12345678H линия?

следующий фрагмент кода доказывает это:

    int main (int argc, char * argv)
    {
        союз {
            char ca [4];
            Т т;
        } А;
        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]) ;
        вернуть 0;
    }

результат: 0x78, 0x56, 0x34, 0x12

  • после удаления предварительного объявления enum T и перемещения определения функции foo после определения enum T: результат в порядке:

вышеуказанная ключевая инструкция становится:

mov BYTE PTR [eax], 120; 00000078H

конечный результат: 0x78, 0x1, 0x1, 0x1

Обратите внимание, что значение не перезаписывается

Поэтому использование форвард-декларации enum в VC считается вредным.

Кстати, не удивительно, что синтаксис для объявления базового типа такой же, как в C #. На практике я обнаружил, что стоит сэкономить 3 байта, указав базовый тип как char при обращении к встроенной системе, которая ограничена в памяти.

zhaorufei
источник
1

В своих проектах я применил метод Перечисления, привязанного к пространству имен , для работы с enumустаревшими и сторонними компонентами. Вот пример:

forward.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// 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_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#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;
    }
}

main.cc:

#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.

mavam
источник
0

Мое решение вашей проблемы было бы либо:

1 - используйте int вместо enums: объявите свои 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: создать полный класс с ограниченными экземплярами const, как в Java. Форвард объявить класс, а затем определить его в файле CPP и создать только значения, подобные enum. Я сделал что-то подобное в C ++, и результат был не таким удовлетворительным, как хотелось бы, так как требовался некоторый код для имитации перечисления (конструкция копирования, оператор = и т. Д.).

3: Как предложено ранее, используйте объявленный в частном порядке enum. Несмотря на то, что пользователь увидит его полное определение, он не сможет ни использовать его, ни использовать приватные методы. Таким образом, вы обычно сможете изменять перечисление и содержимое существующих методов без необходимости перекомпиляции кода с использованием вашего класса.

Я думаю, будет решение 3 или 1.

paercebal
источник
-1

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

Таким образом, компилятор не может даже позволить вам объявить перечисление вперед и указать на него указатель, потому что даже там ему нужен размер перечисления.

Карл Селеборг
источник
-1

Вы определяете перечисление, чтобы ограничить возможные значения элементов типа ограниченным набором. Это ограничение должно применяться во время компиляции.

При прямом объявлении факта, что вы будете использовать «ограниченный набор» позже, не добавляется никакого значения: последующий код должен знать возможные значения, чтобы извлечь из него выгоду.

Несмотря на то, что компилятор будет обеспокоен размером перечисляемого типа, то цель перечисления теряется , когда вы вперед объявить.

xtofl
источник
1
Нет, последующему коду не нужно знать значения, чтобы это было полезно - в частности, если последующий код является просто прототипом функции, принимающим или возвращающим параметры перечисления, размер типа не важен. Использование предварительного объявления здесь может удалить зависимости сборки, ускоряя компиляцию.
j_random_hacker
Ты прав. Целью является не подчиняться значениям, а типу. Решено с 0x типов Enum.
xtofl