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

<p>я пытаюсь сделать что-то вроде следующего:</p><pre><code>enum e; void foo(e e); enum e {a, b, c}; </code></pre><p>который компилятор отклоняет. я быстро посмотрел на google, и консенсус, кажется, "вы не можете сделать это", но я не могу понять, почему. кто-нибудь может объяснить?</p><p>пояснение 2: я делаю это, поскольку у меня есть закрытые методы в классе, которые принимают указанное перечисление, и я не хочу, чтобы значения перечисления отображались, поэтому, например, я не хочу, чтобы кто-нибудь знал, что e определяется как</p><pre><code>enum e { functionality\_normal, functionality\_restricted, functionality\_for\_project\_x } </code></pre><p>поскольку проект x - это не то, о чем я хочу, чтобы мои пользователи знали.</p><p>итак, я хотел переслать объявление enum, чтобы я мог поместить приватные методы в файл заголовка, объявить enum внутри cpp и распространить файл и заголовок встроенной библиотеки людям.</p><p>что касается компилятора - это gcc.</p>

<p>причина, по которой перечисление не может быть объявлено заблаговременно, заключается в том, что, не зная значений, компилятор не может узнать, какое хранилище требуется для переменной перечисления. компиляторам c++ разрешено указывать фактическое пространство хранения в зависимости от размера, необходимого для хранения всех указанных значений. если все, что видно, это предварительное объявление, модуль перевода не может знать, какой размер хранилища будет выбран - это может быть char, int или что-то еще.</p><hr><p>из раздела 7.2.5 стандарта iso c++:</p><blockquote> <p><em> базовый тип </em> перечисления является целочисленным типом, который может представлять все значения перечислителя, определенные в перечислении. определяется реализацией, какой интегральный тип используется в качестве базового типа для перечисления, за исключением того, что базовый тип не должен быть больше <code>int</code>, если значение перечислителя не может вписаться в <code>int</code> или <code>unsigned int</code>. если список перечисления <em> </em> пуст, базовый тип выглядит так, как если бы перечисление имело единственный перечислитель со значением 0. значение <code>sizeof()</code>, примененное к перечислению type, объект типа перечисления или перечислитель - это значение <code>sizeof()</code>, применяемое к базовому типу.</p> </blockquote><p>поскольку вызывающий <em> </em> функции должен знать размеры параметров для правильной настройки стека вызовов, число перечислений в списке перечислений должно быть известно до прототип функции.</p><p>обновление: в c++ 0x был предложен и принят синтаксис для объявляющих заранее типов enum. вы можете увидеть это предложение по адресу <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf">http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf</a> </p>

<p>прямое объявление перечислений также возможно в c++ 0x. ранее причина, по которой типы перечислений не могли быть объявлены вперед, заключается в том, что размер перечисления зависит от его содержимого. пока размер перечисления определяется приложением, оно может быть объявлено заранее:</p><pre><code>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. </code></pre>

<p>я добавляю актуальный ответ здесь, учитывая последние события.</p><p>вы можете заранее объявить перечисление в c++ 11, при условии, что вы одновременно объявляете его тип хранения. синтаксис выглядит так:</p><pre><code>enum e : short; void foo(e e); .... enum e : short { value\_1, value\_2, .... } </code></pre><p>фактически, если функция никогда не ссылается на значения перечисления, вам вообще не нужно полное объявление в этой точке.</p><p>это поддерживается g ++ 4.6 и более поздними версиями (<code>-std=c++0x</code> или <code>-std=c++11</code> в более поздних версиях). visual c++ 2013 поддерживает это; в более ранних версиях у него была какая-то нестандартная поддержка, которую я еще не понял - я нашел несколько предположений о том, что простое предварительное объявление допустимо, но ymmv.</p>

<p>прямое объявление вещей в c++ очень полезно, потому что это <a href="http://brianbondy.com/blog/id/5/slow-compilation-time">значительно ускоряет время компиляции</a> . вы можете переслать объявление нескольких вещей в c++, включая: <code>struct</code>, <code>class</code>, <code>function</code> и т.д.</p><p>но можете ли вы объявить <code>enum</code> в c++?</p><p>нет, ты не можешь.</p><p>но почему бы не позволить это? если бы это было разрешено, вы могли бы указать свой тип <code>enum</code> в своем заголовочном файле и значения <code>enum</code> в исходном файле. похоже, это должно быть разрешено правильно?</p><p>неправильно.</p><p>в c++ нет типа по умолчанию для <code>enum</code>, как в c # (int). в c++ ваш тип <code>enum</code> будет определен компилятором как любой тип, который будет соответствовать диапазону значений, которые вы имеете для своего <code>enum</code>.</p><p>что это значит?</p><p>это означает, что базовый тип вашего <code>enum</code> не может быть полностью определен, пока не будут определены все значения <code>enum</code>. кто из вас не может отделить объявление и определение вашего <code>enum</code>. и поэтому вы не можете переслать объявление <code>enum</code> в c++.</p><p>стандарт iso c++ s7.2.5:</p><blockquote> <p>базовый тип перечисления является целочисленным типом, который может представлять все значения перечислителя, определенные в перечислении. определяется реализацией, какой интегральный тип используется в качестве базового типа для перечисления, за исключением того, что базовый тип не должен быть больше <code>int</code>, если значение перечислителя не может вписаться в <code>int</code> или <code>unsigned int</code>. если список-перечислитель пуст, базовый тип выглядит так, как если бы перечисление имело единственный перечислитель со значением 0. значение <code>sizeof()</code>, примененное к типу перечисления, объекту типа перечисления или перечислителю, является значением <code>sizeof()</code> apply. к основному типу.</p> </blockquote><p>вы можете определить размер перечислимого типа в c++ с помощью оператора <code>sizeof</code>. размер перечисляемого типа - это размер его базового типа. таким образом, вы можете угадать, какой тип ваш компилятор использует для вашего <code>enum</code>.</p><p>что если вы укажете тип вашего <code>enum</code> явно так:</p><pre><code>enum color : char { red=0, green=1, blue=2}; assert(sizeof color == 1); </code></pre><p>можете ли вы затем отправить объявление <code>enum</code>?</p><p>нет. но почему нет?</p><p>указание типа <code>enum</code> на самом деле не является частью текущего стандарта c++. это расширение vc++. это будет частью c++ 0x, хотя.</p><p> <a href="http://brianbondy.com/blog/id/93/forward-declaring-enums-in-c">источник</a> </p>

<p>[мой ответ неправильный, но я оставил его здесь, потому что комментарии полезны].</p><p>прямое объявление перечислений не является стандартным, поскольку указатели на разные типы перечислений не гарантируют одинаковый размер. компилятору может понадобиться увидеть определение, чтобы знать, какие указатели размера могут использоваться с этим типом.</p><p>на практике, по крайней мере, на всех популярных компиляторах указатели на перечисления имеют одинаковый размер. например, в visual c++ прямое объявление перечислений предоставляется как расширение языка.</p>

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

<p>просто отметив, что на самом деле причина <em>есть</em> в том, что размер перечисления еще не известен после предварительного объявления. ну, вы используете прямое объявление структуры, чтобы иметь возможность передавать указатель или ссылаться на объект из того места, на которое ссылаются в самом объявлении прямого определения структуры.</p><p>объявление перечисления не было бы слишком полезным, потому что хотелось бы иметь возможность обойти перечисление по значению. вы даже не могли иметь указатель на него, потому что мне недавно сказали, что некоторые платформы используют указатели разного размера для char, чем для int или long. так что все зависит от содержания перечисления.</p><p>текущий стандарт c++ явно запрещает делать что-то вроде</p><pre><code>enum x; </code></pre><p>(в <code>7.1.5.3/1</code>). но следующий стандарт c++, который должен выйти в следующем году, позволяет следующее, что убедило меня, что проблема на самом деле <em>имеет</em> с базовым типом:</p><pre><code>enum x : int; </code></pre><p>это известно как "непрозрачное" объявление перечисления. вы даже можете использовать x <em>по значению</em> в следующем коде. и его перечислители могут быть позже определены в последующем повторном объявлении перечисления. смотрите <code>7.2</code> в текущем рабочем проекте.</p>

<p>я бы сделал это так:</p><p>[в публичном заголовке]</p><pre><code>typedef unsigned long e; void foo(e e); </code></pre><p>[во внутреннем заголовке]</p><pre><code>enum econtent { functionality\_normal, functionality\_restricted, functionality\_for\_project\_x, force\_32bit = 0xffffffff }; </code></pre><p>добавляя force\_32bit, мы гарантируем, что econtent компилируется в long, поэтому он взаимозаменяем с e.</p>

<p>кажется, это не может быть заранее объявлено в gcc!</p><p>интересная дискуссия <a href="http://bytes.com/forum/thread62285.html">здесь</a> </p>

<p>вы можете обернуть перечисление в структуру, добавив некоторые конструкторы и преобразования типов, и вместо этого объявить структуру вперед.</p><pre><code>#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; \ } </code></pre><p>это похоже на работу: <a href="http://ideone.com/tytp2">http://ideone.com/tytp2</a> </p>

<p>если вы действительно не хотите, чтобы ваш enum появлялся в вашем заголовочном файле и гарантировал, что он используется только частными методами, тогда одним из решений может быть использование принципа pimpl.</p><p>это метод, который гарантирует скрытие внутренних элементов класса в заголовках, просто объявив:</p><pre><code>class a { public: ... private: void* pimpl; }; </code></pre><p>затем в вашем файле реализации (cpp) вы объявляете класс, который будет представлять внутреннее устройство.</p><pre><code>class aimpl { public: aimpl(a* pthis): m\_pthis(pthis) {} ... all private methods here ... private: a* m\_pthis; }; </code></pre><p>вы должны динамически создать реализацию в конструкторе класса и удалить ее в деструкторе, а при реализации открытого метода вы должны использовать:</p><pre><code>((aimpl*)pimpl)->privatemethod(); </code></pre><p>есть плюсы в использовании pimpl, во-первых, он отделяет заголовок вашего класса от его реализации, нет необходимости перекомпилировать другие классы при изменении реализации одного класса. другое - это ускоряет ваше время компиляции, потому что ваши заголовки очень просты.</p><p>но это неудобно в использовании, поэтому вы должны спросить себя, не является ли проблема просто объявлением вашего enum как private в заголовке.</p>

<p>в моих проектах я использовал метод <a href="http://www.ddj.com/cpp/184403894">namespace-bound enumeration</a> для работы с <code>enum</code>s из устаревших и сторонних компонентов. вот пример:</p><h3>forward.h:</h3><pre><code>namespace type { class legacy\_type; typedef const legacy\_type& type; } </code></pre><h3>enum.h:</h3><pre><code>// 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\_; }; } </code></pre><h3>foo.h:</h3><pre><code>#include "forward.h" class foo { public: void f(type::type t); }; </code></pre><h3>foo.cc:</h3><pre><code>#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; } } </code></pre><h3>main.cc:</h3><pre><code>#include "foo.h" #include "enum.h" int main() { foo fu; fu.f(legacy::x); return 0; } </code></pre><p>обратите внимание, что заголовок <code>foo.h</code> не должен ничего знать о <code>legacy::evil</code>. только файлы, которые используют устаревший тип <code>legacy::evil</code> (здесь: main.cc), должны включать <code>enum.h</code>.</p>

<p>для vc вот тест для предварительного объявления и определения базового типа:</p><ol> <li>следующий код скомпилирован нормально.</li> </ol><pre> typedef int myint; enum t; void foo (t * tp) { * tp = (t) 0x12345678; } enum t: char { a }; </pre><p>но получил предупреждение для/w4 (/ w3 не несет это предупреждение)</p><p>предупреждение c4480: используется нестандартное расширение: указание базового типа для перечисления 't'</p><ol start="2"> <li> <p>vc (microsoft (r) 32-битный оптимизирующий компилятор c/c++ версии 15.00.30729.01 для 80x86) выглядит некорректно в приведенном выше случае:</p> <ul> <li>при просмотре enum t; vc предполагает, что тип перечисления t использует 4 байта по умолчанию int в качестве базового типа, поэтому сгенерированный код сборки:</li> </ul> </li> </ol><pre> ? foo @@ yaxpaw4t @@@ z proc; foo ; файл e:\work\c\_cpp\cpp\_snippet.cpp ; строка 13 нажмите ebp mov ebp, esp ; строка 14 mov eax, dword ptr \_tp $ [ebp] mov dword ptr [eax], 305419896; 12345678h ; строка 15 pop ebp ret 0 ? foo @@ yaxpaw4t @@@ z endp; foo </pre><p>приведенный выше ассемблерный код извлечен непосредственно из /fatest.asm, а не мое личное предположение. вы видите mov dword ptr [eax], 305419896; 12345678h линия?</p><p>следующий фрагмент кода доказывает это:</p><pre> 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; } </pre><p>результат: 0x78, 0x56, 0x34, 0x12</p><ul> <li>после удаления предварительного объявления enum t и перемещения определения функции foo после определения enum t: результат в порядке:</li> </ul><p>вышеуказанная ключевая инструкция становится:</p><p>mov byte ptr [eax], 120; 00000078h</p><p>конечный результат: 0x78, 0x1, 0x1, 0x1</p><p>обратите внимание, что значение не перезаписывается</p><p>поэтому использование предварительного объявления enum в vc считается вредным.</p><p>кстати, не удивительно, что синтаксис для объявления базового типа такой же, как в c #. на практике я обнаружил, что стоит сэкономить 3 байта, указав базовый тип как char при обращении к встроенной системе, которая ограничена в памяти.</p>

<p>есть некоторое несогласие, так как это столкнулось (вроде), так что вот некоторые важные моменты из стандарта. исследования показывают, что стандарт на самом деле не определяет прямое объявление, и при этом он явно не заявляет, что перечисления могут или не могут быть объявлены заранее.</p><p>сначала из dcl.enum, раздел 7.2:</p><blockquote> <p>базовый тип перечисления является целочисленным типом, который может представлять все значения перечислителя, определенные в перечислении. это определяется реализацией, какой целочисленный тип используется в качестве базового типа для перечисления, за исключением того, что базовый тип не должен быть больше, чем int, если значение перечислителя не может поместиться в int или unsigned int. если список перечислителя пуст, базовый тип выглядит так, как если бы перечисление имело единственный перечислитель со значением 0. значение sizeof (), примененное к типу перечисления, объекту типа перечисления или перечислителю, является значением sizeof () применяется к базовому типу.</p> </blockquote><p>таким образом, базовый тип перечисления определяется реализацией с одним небольшим ограничением.</p><p>далее мы переходим к разделу "неполные типы" (3.9), который примерно так же близок, как мы подходим к любому стандарту на предварительные объявления:</p><blockquote> <p>класс, который был объявлен, но не определен, или массив неизвестного размера или неполного типа элемента, является не полностью определенным типом объекта.</p> <p>тип класса (такой как "класс x") может быть неполным в одной точке в единице перевода и завершаться позже; тип "класс x" является одним и тем же типом в обеих точках. объявленный тип объекта массива может быть массивом неполного типа класса и, следовательно, неполным; если тип класса завершается позже в модуле перевода, тип массива становится завершенным; тип массива в этих двух точках одинакового типа. объявленный тип объекта массива может быть массивом неизвестного размера и, следовательно, быть неполным в одной точке единицы преобразования и завершаться позже; типы массивов в этих двух точках ("массив неизвестной границы t" и "массив n t") - это разные типы. тип указателя на массив неизвестного размера или тип, определенный объявлением typedef как массив неизвестного размера, не может быть завершен.</p> </blockquote><p>таким образом, стандарт в значительной степени изложил типы, которые могут быть объявлены заранее. enum там не было, поэтому авторы компилятора обычно считают предварительное объявление запрещенным стандартом из-за переменного размера его базового типа.</p><p>это тоже имеет смысл. на перечисления обычно ссылаются в ситуациях с побочными значениями, и компилятору действительно нужно знать размер хранилища в этих ситуациях. поскольку размер хранилища определяется реализацией, многие компиляторы могут просто использовать 32-битные значения для базового типа каждого перечисления, после чего становится возможным их прямое объявление. интересным экспериментом может быть попытка объявить enum в визуальной студии, а затем заставить его использовать базовый тип, больший чем sizeof (int), как описано выше, чтобы увидеть, что происходит.</p>

<p>мое решение вашей проблемы было бы либо:</p><p>1 - используйте int вместо enums: объявите свои int в анонимном пространстве имен в вашем файле cpp (не в заголовке):</p><pre><code>namespace { const int functionality\_normal = 0 ; const int functionality\_restricted = 1 ; const int functionality\_for\_project\_x = 2 ; } </code></pre><p>поскольку ваши методы являются частными, никто не будет связываться с данными. вы можете даже пойти дальше, чтобы проверить, отправляет ли вам кто-то неверные данные:</p><pre><code>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) ; } } </code></pre><p>2: создать полный класс с ограниченными экземплярами const, как в java. форвард объявить класс, а затем определить его в файле cpp и создать только значения, подобные enum. я сделал что-то подобное в c++, и результат был не таким удовлетворительным, как хотелось бы, так как требовался некоторый код для имитации перечисления (конструкция копирования, оператор = и т.д.).</p><p>3: как предложено ранее, используйте объявленный в частном порядке enum. несмотря на то, что пользователь увидит его полное определение, он не сможет ни использовать его, ни использовать приватные методы. таким образом, вы обычно сможете изменять перечисление и содержимое существующих методов без необходимости перекомпиляции кода с использованием вашего класса.</p><p>я думаю, будет решение 3 или 1.</p>