it-swarm-ru.tech

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

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

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.

248
szevvy

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


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

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

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

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

201
KJAWolf

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

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

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

72
Tom

Прямое объявление вещей в 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() apply. к основному типу.

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

Что если вы укажете тип вашего enum явно так:

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

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

Нет. Но почему нет?

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

Источник

30
Brian R. Bondy

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

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

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

13
James Hopkin

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

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

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

7
Alexey Feldgendler

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

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

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

enum X;

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

enum X : int;

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

5
Johannes Schaub - litb

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

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

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.

4
Laurie Cheers

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

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

2
prakash

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

#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

2
Leszek Swirski

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

В моих проектах я использовал метод Namespace-Bound Enumeration для работы с enums из устаревших и сторонних компонентов. Вот пример:

forward.h:

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

enum.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_;
    };
}

foo.h:

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

Для 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) выглядит некорректно в приведенном выше случае:

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

Приведенный выше ассемблерный код извлечен непосредственно из /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: результат в порядке:

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

mov BYTE PTR [eax], 120; 00000078H

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

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

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

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

1
zhaorufei

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

Сначала из dcl.enum, раздел 7.2:

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

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

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

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

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

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

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

1
Dan Olson

Мое решение вашей проблемы было бы либо:

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

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

Я думаю, будет решение 3 или 1.

0
paercebal