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

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

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.

Ответ 1

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


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

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

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

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

Ответ 2

В С++ 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.

Ответ 3

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

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

Ответ 4

Переслать декларацию на С++ очень полезно, потому что значительно ускоряет время компиляции. Вы можете переслать объявление в С++, в том числе: struct, class, function и т.д.

Но можете ли вы переслать объявление enum в С++?

Нет, вы не можете.

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

Неправильно.

В С++ нет типа по умолчанию для enum, как в С# (int). В С++ ваш тип enum будет определяться компилятором любым типом, который будет соответствовать диапазону значений, которые у вас есть для enum.

Что это значит?

Это означает, что ваш базовый тип enum не может быть полностью определен до тех пор, пока не будут определены все значения enum. Что вы не можете отделить декларацию и определение своего enum. И поэтому вы не можете перенаправить объявление enum в С++.

Стандарт ISO С++ S7.2.5:

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

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

Что делать, если явно указать тип вашего enum следующим образом:

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

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

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

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

Источник

Ответ 5

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

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

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

Ответ 6

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

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

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

Ответ 7

Я бы сделал это следующим образом:

[в общедоступном заголовке]

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.

Ответ 8

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

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

В текущем стандарте С++ явно запрещается делать что-то вроде

enum X;

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

enum X : int;

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

Ответ 9

Похоже, что он не может быть пролонгирован в GCC!

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

Ответ 10

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

Ответ 11

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

#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

Ответ 12

Мое решение вашей проблемы состояло бы в следующем:

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 (copy construction, operator = и т.д.) Необходим некоторый код.

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

Мое предположение было бы либо решением 3, либо 1.

Ответ 13

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

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

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

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

Далее мы переходим к разделу "неполные типы" (3.9), которое примерно так же близко, как мы приходим к любому стандарту при форвардных объявлениях:

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

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

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

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

Ответ 14

Для VC, здесь тест о форвардной декларации и указании базового типа:

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

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

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

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

    • при просмотре перечисления T; VC предполагает, что тип перечисления T использует по умолчанию 4 байта int в качестве базового типа, поэтому сгенерированный ассемблерный код:
    [email protected]@[email protected]@@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
    [email protected]@[email protected]@@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

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

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

mov BYTE PTR [eax], 120; 00000078H

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

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

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

Кстати, не удивительно, что синтаксис объявления базового типа такой же, как и на С#. В pratice я счел целесообразным сохранить 3 байта, указав базовый тип как char при разговоре со встроенной системой, которая ограничена памятью.

Ответ 15

В моих проектах я принял метод

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.

Ответ 16

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

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

Ответ 17

В ответ на пояснение: если вы используете только enum только для внутреннего использования, почему бы не объявить его внутри класса как private?

Ответ 18

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

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

Хотя компилятор обеспокоен размером перечисляемого типа, намерение перечисления теряется, когда вы его отправляете.