Объявление перечисления в классе

151

В следующем фрагменте кода Colorперечисление объявляется внутри Carкласса, чтобы ограничить область действия перечисления и попытаться не «загрязнять» глобальное пространство имен.

class Car
{
public:

   enum Color
   {
      RED,
      BLUE,
      WHITE
   };

   void SetColor( Car::Color color )
   {
      _color = color;
   }

   Car::Color GetColor() const
   {
      return _color;
   }

private:

   Car::Color _color;

};

(1) Это хороший способ ограничить область действия Colorперечисления? Или я должен объявить это вне Carкласса, но, возможно, в его собственном пространстве имен или структуре? Я только что наткнулся на эту статью сегодня, которая поддерживает последнюю и обсуждает некоторые приятные моменты о перечислениях: http://gamesfromwithin.com/stupid-c-tricks-2-better-enums .

(2) В этом примере при работе в классе лучше всего кодировать перечисление как Car::Color, или этого будет Colorдостаточно? (Я предполагаю, что первое лучше, на случай, если Colorв глобальном пространстве имен объявлен другой enum. Таким образом, по крайней мере, мы явно говорим о перечислении, на которое мы ссылаемся.)

bporter
источник

Ответы:

86
  1. Если Colorэто что-то, что специфично только для Cars, то таким образом вы бы ограничивали его область действия. Если у вас будет другое Colorперечисление, которое используют другие классы, вы можете также сделать его глобальным (или, по крайней мере, за его пределами Car).

  2. Это не имеет значения. Если есть глобальный, то локальный все равно используется, так как он ближе к текущей области. Обратите внимание, что если вы определяете эти функции вне определения класса, вам нужно явно указать Car::Colorих интерфейс.

Петр Александр
источник
12
2. Да и нет. Car::Color getColor()а void Car::setColor(Color c)потому что у setColorнас уже есть спецификатор.
Матье М.
84

В настоящее время - используя C ++ 11 - вы можете использовать для этого класс enum :

enum class Color { RED, BLUE, WHITE };

AFAII это делает именно то, что вы хотите.

Андреас Флорат
источник
3
К сожалению, он не разрешает функции-члены: stackoverflow.com/a/53284026/7395227
Андреас
66

Я предпочитаю следующий подход (код ниже). Он решает проблему «загрязнения пространства имен», но также он намного более безопасен для типов (вы не можете назначать и даже сравнивать два различных перечисления или ваше перечисление с любыми другими встроенными типами и т. Д.).

struct Color
{
    enum Type
    {
        Red, Green, Black
    };
    Type t_;
    Color(Type t) : t_(t) {}
    operator Type () const {return t_;}
private:
   //prevent automatic conversion for any other built-in types such as bool, int, etc
   template<typename T>
    operator T () const;
};

Использование:

Color c = Color::Red;
switch(c)
{
   case Color::Red:
     //некоторый код
   break;
}
Color2 c2 = Color2::Green;
c2 = c; //error
c2 = 3; //error
if (c2 == Color::Red ) {} //error
If (c2) {} error

Я создаю макрос для облегчения использования:

#define DEFINE_SIMPLE_ENUM(EnumName, seq) \
struct EnumName {\
   enum type \
   { \
      BOOST_PP_SEQ_FOR_EACH_I(DEFINE_SIMPLE_ENUM_VAL, EnumName, seq)\
   }; \
   type v; \
   EnumName(type v) : v(v) {} \
   operator type() const {return v;} \
private: \
    template<typename T> \
    operator T () const;};\

#define DEFINE_SIMPLE_ENUM_VAL(r, data, i, record) \
    BOOST_PP_TUPLE_ELEM(2, 0, record) = BOOST_PP_TUPLE_ELEM(2, 1, record),

Использование:

DEFINE_SIMPLE_ENUM(Color,
             ((Red, 1))
             ((Green, 3))
             )

Некоторые ссылки:

  1. Херб Саттер, Jum Hyslop, C / C ++ Users Journal, 22 (5), май 2004 г.
  2. Херб Саттер, Дэвид Э. Миллер, Бьярн Страуструп, сильно типизированные перечисления (редакция 3), июль 2007 г.
Сергей Тепляков
источник
Мне это нравится. Это также вынуждает перечисление создавать действительное значение. Я думаю, что оператор присваивания и конструктор копирования были бы полезны. Также т_ должно быть приватным. Макросы, без которых я могу обойтись.
jmucchiello
Мне тоже это нравится. Спасибо за ссылки.
anio
1
Вы сказали: «Кроме того, он гораздо более безопасен для типов (вы не можете назначать и даже сравнивать два разных перечисления ...» . Почему вы считаете, что это хорошая функция? Я думаю, что if(c2 == Color::Red )она разумна и должна компилироваться, но в вашем примере это не. Тот же аргумент для назначения также!
Наваз
3
@Nawaz c2относится к другому типу ( Color2), так почему вы думаете, что c2 == Color::Redзадания должны компилироваться? Что если Color::Red1, а Color2::Red2? Следует Color::Red == Color2::Redоценить trueили false? Если вы смешиваете нетипобезопасные счетчики, у вас будет плохое время.
Виктор К
2
Почему нет Тип t_; частный?
Зингам
7

В общем, я всегда помещаю свои перечисления в struct. Я видел несколько рекомендаций, включая «префикс».

enum Color
{
  Clr_Red,
  Clr_Yellow,
  Clr_Blue,
};

Всегда думал, что это больше похоже на Cрекомендации, чем на C++них (например, из-за аббревиатуры, а также из-за пространств имен в C++).

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

  • Пространства имен
  • Структуры / классы

Лично я склонен использовать a, structпотому что он может использоваться в качестве параметров для программирования шаблонов, в то время как пространством имен нельзя манипулировать.

Примеры манипуляции включают в себя:

template <class T>
size_t number() { /**/ }

который возвращает количество элементов enum внутри структуры T:)

Матье М.
источник
3

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

Харви
источник