Я пытаюсь сделать что-то вроде следующего:
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.
Ответы:
Причина, по которой перечисление не может быть объявлено заблаговременно, заключается в том, что без знания значений компилятор не может узнать, какое хранилище требуется для переменной перечисления. Компиляторам C ++ разрешено указывать фактическое пространство хранения на основе размера, необходимого для хранения всех указанных значений. Если все, что видно, - это предварительное объявление, модуль перевода не может знать, какой объем хранилища будет выбран - это может быть char, int или что-то еще.
Из раздела 7.2.5 стандарта ISO C ++:
Поскольку вызывающая функция должна знать размеры параметров для правильной настройки стека вызовов, число перечислений в списке перечислений должно быть известно до прототипа функции.
Обновление: в C ++ 0X был предложен и принят синтаксис для объявляющих заранее типов enum. Вы можете увидеть это предложение по адресу http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf.
источник
struct S; void foo(S s);
(обратите внимание, чтоfoo
это только объявлено, но не определено), то нет никаких причин, почему мы не могли бы сделатьenum E; void foo(E e);
это также. В обоих случаях размер не нужен.Предварительное объявление перечислений возможно начиная с C ++ 11. Ранее причина, по которой типы перечислений не могли быть объявлены вперед, заключается в том, что размер перечисления зависит от его содержимого. Пока размер перечисления определяется приложением, он может быть заранее объявлен:
источник
Я добавляю актуальный ответ здесь, учитывая последние события.
Вы можете заранее объявить перечисление в C ++ 11, если вы одновременно объявляете его тип хранения. Синтаксис выглядит так:
Фактически, если функция никогда не ссылается на значения перечисления, вам вообще не нужно полное объявление в этой точке.
Это поддерживается G ++ 4.6 и более поздними версиями (
-std=c++0x
или-std=c++11
более поздними версиями). Visual C ++ 2013 поддерживает это; в более ранних версиях у него была какая-то нестандартная поддержка, которую я еще не понял - я обнаружил некоторые предположения, что простое предварительное объявление является законным, но YMMV.источник
enum class
как расширение C ++ (до C ++ 11 отличалосьenum class
), по крайней мере, если я правильно помню. Компилятор позволял вам указывать базовый тип перечисления, но не поддерживалenum class
перечисления и не объявлял их заранее и предупреждал, что квалификация перечислителя с областью действия перечисления является нестандартным расширением. Я помню, что он работает примерно так же, как указание базового типа в C ++ 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:
Вы можете определить размер перечисляемого типа в C ++ с помощью
sizeof
оператора. Размер перечисляемого типа - это размер его базового типа. Таким образом, вы можете угадать, какой тип ваш компилятор использует для вашегоenum
.Что, если вы укажете тип вашего
enum
явно так:Можете ли вы затем объявить свое
enum
?Нет. Но почему нет?
Указание типа an на
enum
самом деле не является частью текущего стандарта C ++. Это расширение VC ++. Это будет частью C ++ 0x.Источник
источник
[Мой ответ неверный, но я оставил его здесь, потому что комментарии полезны].
Прямое объявление перечислений не является стандартным, поскольку указатели на разные типы перечислений не гарантируют одинаковый размер. Компилятору может понадобиться увидеть определение, чтобы узнать, какие указатели размера могут использоваться с этим типом.
На практике, по крайней мере, на всех популярных компиляторах указатели на перечисления имеют одинаковый размер. Прямое объявление перечислений предоставляется, например, как расширение языка Visual C ++.
источник
В действительности нет такой вещи, как предварительная декларация enum. Поскольку определение enum не содержит кода, который мог бы зависеть от другого кода, использующего enum, обычно нет проблем с полным определением enum при его первом объявлении.
Если единственное использование вашего enum - это закрытые функции-члены, вы можете реализовать инкапсуляцию, если сам enum будет закрытым членом этого класса. Перечисление все еще должно быть полностью определено в точке объявления, то есть в пределах определения класса. Тем не менее, это не большая проблема, так как объявление частных функций-членов там, и не хуже демонстрации внутренних функций реализации, чем это.
Если вам нужна более глубокая степень скрытия для деталей вашей реализации, вы можете разбить его на абстрактный интерфейс, состоящий только из чисто виртуальных функций и конкретного, полностью скрытого, класса, реализующего (наследующего) интерфейс. Создание экземпляров класса может быть обработано фабрикой или статической функцией-членом интерфейса. Таким образом, даже настоящее имя класса, не говоря уже о его частных функциях, не будет открыто.
источник
Просто отметив, что причина на самом деле в том, что размер перечисления еще не известен после предварительного объявления. Ну, вы используете прямое объявление структуры, чтобы иметь возможность передавать указатель или ссылаться на объект из того места, на которое ссылаются в самом объявлении прямого определения структуры.
Объявление перечисления не было бы слишком полезным, потому что хотелось бы иметь возможность передавать перечисление по значению. Вы даже не могли иметь указатель на него, потому что мне недавно сказали, что некоторые платформы используют указатели разного размера для char, чем для int или long. Так что все зависит от содержания перечисления.
Текущий стандарт C ++ явно запрещает делать что-то вроде
(в
7.1.5.3/1
). Но на следующем стандарт C ++ из - за следующий год позволяет следующее, что убедило меня проблему на самом деле имеет дело с базовым типом:Это известно как "непрозрачная" декларация перечисления. Вы даже можете использовать X по значению в следующем коде. И его перечислители могут быть позже определены в последующем повторном объявлении перечисления. Смотрите
7.2
в текущем рабочем проекте.источник
Я бы сделал это так:
[в публичном заголовке]
[во внутреннем заголовке]
Добавляя FORCE_32BIT, мы гарантируем, что Econtent скомпилируется в long, поэтому он взаимозаменяем с E.
источник
Если вы действительно не хотите, чтобы ваш enum появлялся в вашем заголовочном файле и гарантировал, что он используется только частными методами, тогда одним из решений может быть использование принципа pimpl.
Это метод, который гарантирует скрытие внутренних элементов класса в заголовках, просто объявив:
Затем в вашем файле реализации (cpp) вы объявляете класс, который будет представлять внутреннее устройство.
Вы должны динамически создать реализацию в конструкторе класса и удалить ее в деструкторе, а при реализации открытого метода вы должны использовать:
Есть плюсы в использовании pimpl, во-первых, он отделяет заголовок вашего класса от его реализации, нет необходимости перекомпилировать другие классы при изменении реализации одного класса. Другое - это ускоряет ваше время компиляции, потому что ваши заголовки очень просты.
Но это неудобно в использовании, поэтому вы должны спросить себя, не является ли проблема просто объявлением вашего enum как private в заголовке.
источник
Вы можете обернуть перечисление в структуру, добавив некоторые конструкторы и преобразования типов, и вместо этого объявить структуру вперед.
Это похоже на работу: http://ideone.com/TYtP2
источник
Кажется, это не может быть заранее объявлено в GCC!
Интересная дискуссия здесь
источник
Есть некоторое несогласие, так как это столкнулось (вроде), так что вот некоторые важные моменты из стандарта. Исследования показывают, что стандарт на самом деле не определяет предварительную декларацию, и при этом он явно не заявляет, что перечисления могут или не могут быть объявлены форвард.
Во-первых, из dcl.enum, раздел 7.2:
Таким образом, базовый тип перечисления определяется реализацией с одним небольшим ограничением.
Далее мы перейдем к разделу «Неполные типы» (3.9), который примерно так же близок, как мы подошли к любому стандарту на предварительные объявления:
Таким образом, стандарт в значительной степени изложил типы, которые могут быть объявлены заранее. Enum там не было, поэтому авторы компилятора обычно считают объявление вперед запрещенным стандартом из-за переменного размера его базового типа.
Это тоже имеет смысл. На перечисления обычно ссылаются в ситуациях побочного значения, и компилятору действительно нужно знать размер хранилища в этих ситуациях. Поскольку размер хранилища определяется реализацией, многие компиляторы могут просто использовать 32-битные значения для базового типа каждого перечисления, после чего становится возможным их прямое объявление. Интересным экспериментом может быть попытка объявить enum в визуальной студии, а затем заставить его использовать базовый тип, больший чем sizeof (int), как объяснено выше, чтобы увидеть, что происходит.
источник
Для VC вот тест на предварительное объявление и определение базового типа:
Но получил предупреждение для / W4 (/ W3 не несет это предупреждение)
предупреждение C4480: используется нестандартное расширение: указание базового типа для перечисления 'T'
VC (32-разрядный оптимизирующий компилятор C / C ++ версии 15.00.30729.01 для 80x86) выглядит некорректно в приведенном выше случае:
Приведенный выше код сборки извлечен непосредственно из /Fatest.asm, а не мое личное предположение. Вы видите mov DWORD PTR [eax], 305419896; 12345678H линия?
следующий фрагмент кода доказывает это:
результат: 0x78, 0x56, 0x34, 0x12
вышеуказанная ключевая инструкция становится:
mov BYTE PTR [eax], 120; 00000078H
конечный результат: 0x78, 0x1, 0x1, 0x1
Обратите внимание, что значение не перезаписывается
Поэтому использование форвард-декларации enum в VC считается вредным.
Кстати, не удивительно, что синтаксис для объявления базового типа такой же, как в C #. На практике я обнаружил, что стоит сэкономить 3 байта, указав базовый тип как char при обращении к встроенной системе, которая ограничена в памяти.
источник
В своих проектах я применил метод Перечисления, привязанного к пространству имен , для работы с
enum
устаревшими и сторонними компонентами. Вот пример:forward.h:
enum.h:
foo.h:
foo.cc:
main.cc:
Обратите внимание, что
foo.h
заголовок не должен ничего знать оlegacy::evil
. Только файлы, которые используют устаревший типlegacy::evil
(здесь: main.cc), должны быть включеныenum.h
.источник
Мое решение вашей проблемы было бы либо:
1 - используйте int вместо enums: объявите свои int в анонимном пространстве имен в вашем файле CPP (не в заголовке):
Поскольку ваши методы являются частными, никто не будет связываться с данными. Вы можете даже пойти дальше, чтобы проверить, отправляет ли вам кто-то неверные данные:
2: создать полный класс с ограниченными экземплярами const, как в Java. Форвард объявить класс, а затем определить его в файле CPP и создать только значения, подобные enum. Я сделал что-то подобное в C ++, и результат был не таким удовлетворительным, как хотелось бы, так как требовался некоторый код для имитации перечисления (конструкция копирования, оператор = и т. Д.).
3: Как предложено ранее, используйте объявленный в частном порядке enum. Несмотря на то, что пользователь увидит его полное определение, он не сможет ни использовать его, ни использовать приватные методы. Таким образом, вы обычно сможете изменять перечисление и содержимое существующих методов без необходимости перекомпиляции кода с использованием вашего класса.
Я думаю, будет решение 3 или 1.
источник
Поскольку перечисление может иметь целочисленный размер различного размера (компилятор решает, какой размер имеет данное перечисление), указатель на перечисление также может иметь различный размер, поскольку это целочисленный тип (символы имеют указатели разного размера на некоторых платформах например).
Таким образом, компилятор не может даже позволить вам объявить перечисление вперед и указать на него указатель, потому что даже там ему нужен размер перечисления.
источник
Вы определяете перечисление, чтобы ограничить возможные значения элементов типа ограниченным набором. Это ограничение должно применяться во время компиляции.
При прямом объявлении факта, что вы будете использовать «ограниченный набор» позже, не добавляется никакого значения: последующий код должен знать возможные значения, чтобы извлечь из него выгоду.
Несмотря на то, что компилятор будет обеспокоен размером перечисляемого типа, то цель перечисления теряется , когда вы вперед объявить.
источник