Вперед, объявляя перечисление в C ++ - PullRequest
248 голосов
/ 16 сентября 2008

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

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.

Ответы [ 17 ]

201 голосов
/ 16 сентября 2008

Причина, по которой перечисление не может быть объявлено вперед, заключается в том, что, не зная значений, компилятор не может узнать объем памяти, необходимый для переменной enum. Компиляторам 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 /apers / 2008 / n2764.pdf

186 голосов
/ 15 августа 2009

Предварительное объявление перечислений также возможно в C ++ 0x. Ранее причина, по которой типы перечислений не могли быть объявлены вперед, заключается в том, что размер перечисления зависит от его содержимого. Пока размер перечисления определяется приложением, он может быть заранее объявлен:

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.
71 голосов
/ 24 июля 2012

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

Вы можете заранее объявить перечисление в 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.

30 голосов
/ 19 декабря 2009

Объявление в 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?

Нет. Но почему бы и нет?

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

Источник

13 голосов
/ 16 сентября 2008

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

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

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

7 голосов
/ 16 сентября 2008

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

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

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

5 голосов
/ 26 марта 2009

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

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

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

enum X;

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

enum X : int;

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

4 голосов
/ 16 сентября 2008

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

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

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.

2 голосов
/ 17 сентября 2008

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

2 голосов
/ 16 сентября 2008

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

Интересное обсуждение здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...