Вопрос

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

enum E;

void Foo(E e);

enum E {A, B, C};

который компилятор отвергает.Я быстро просмотрел Google, и, похоже, консенсус таков: "вы не можете этого сделать", но я не могу понять почему.Кто-нибудь может объяснить?

Разъяснение 2:Я делаю это, поскольку у меня есть частные методы в классе, которые принимают указанное перечисление, и я не хочу, чтобы значения перечисления были доступны - так, например, я не хочу, чтобы кто-либо знал, что E определяется как

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

поскольку project X - это не то, о чем я хочу, чтобы мои пользователи знали.

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

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

Это было полезно?

Решение

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


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

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

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

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

Другие советы

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

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

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

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

Источник

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

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

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

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

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

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

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

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

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

enum X;

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

enum X : int;

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

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

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

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.

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

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

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

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

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

#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

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

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

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

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

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

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

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

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

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

Для VC вот тест на прямое объявление и указание базового типа:

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

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

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

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

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

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

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

БАЙТ перемещения PTR [eax], 120 ;00000078H

конечным результатом является:0x78, 0x1, 0x1, 0x1

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

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

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

В своих проектах я использовал Перечисление, связанное с пространством имен техника, с которой нужно иметь дело enums из устаревших и сторонних компонентов.Вот такой пример:

вперед.h:

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

перечисление.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_;
    };
}

фу.х:

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

Мое решение вашей проблемы состояло бы в том, чтобы либо:

1 - используйте 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-файле и создайте экземпляр только значений, подобных перечислению.Я сделал что-то подобное на C ++, и результат оказался не таким удовлетворительным, как хотелось бы, поскольку для имитации перечисления требовался некоторый код (конструкция копирования, operator = и т.д.).

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top