Deklarowanie wyliczenia w klasie

150

W poniższym fragmencie kodu Colorwyliczenie jest zadeklarowane w Carklasie, aby ograniczyć zakres wyliczenia i nie „zanieczyszczać” globalnej przestrzeni nazw.

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) Czy to dobry sposób na ograniczenie zakresu Colorwyliczenia? A może powinienem zadeklarować go poza Carklasą, ale prawdopodobnie w jego własnej przestrzeni nazw lub strukturze? Właśnie dziś trafiłem na ten artykuł, który opowiada się za tym drugim i omawia kilka fajnych punktów na temat wyliczeń: http://gamesfromwithin.com/stupid-c-tricks-2-better-enums .

(2) W tym przykładzie, podczas pracy w klasie, czy najlepiej jest zakodować wyliczenie jako Car::Color, czy po prostu Colorwystarczy? (Zakładam, że to pierwsze jest lepsze, na wypadek gdyby Colorw globalnej przestrzeni nazw zadeklarowano inne wyliczenie. W ten sposób przynajmniej jasno określamy wyliczenie, do którego się odnosimy).

bporter
źródło

Odpowiedzi:

85
  1. Jeśli Colorjest to coś, co jest specyficzne dla samego Cars, to w ten sposób można ograniczyć jego zakres. Jeśli zamierzasz mieć inne Colorwyliczenie, którego używają inne klasy, równie dobrze możesz ustawić je jako globalne (lub przynajmniej na zewnątrz Car).

  2. To nie robi różnicy. Jeśli istnieje globalny, lokalny jest nadal używany, ponieważ jest bliżej bieżącego zakresu. Zauważ, że jeśli zdefiniujesz te funkcje poza definicją klasy, będziesz musiał jawnie określić Car::Colorw interfejsie funkcji.

Peter Alexander
źródło
12
2. Tak i nie. Car::Color getColor()ale void Car::setColor(Color c)ponieważ setColormamy już specyfikator.
Matthieu M.
84

Obecnie - używając C ++ 11 - możesz użyć do tego klasy enum :

enum class Color { RED, BLUE, WHITE };

AFAII to robi dokładnie to, co chcesz.

Andreas Florath
źródło
2
Niestety, nie zezwala na funkcje członkowskie: stackoverflow.com/a/53284026/7395227
Andreas
66

Wolę następujące podejście (kod poniżej). Rozwiązuje problem "zanieczyszczenia przestrzeni nazw", ale jest też dużo bardziej bezpieczny dla typów (nie możesz przypisać, a nawet porównać dwóch różnych wyliczeń lub wyliczenia z innymi wbudowanymi typami itp.).

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;
};

Stosowanie:

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

Tworzę makro w celu ułatwienia użytkowania:

#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),

Stosowanie:

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

Niektóre odniesienia:

  1. Herb Sutter, Jum Hyslop, C / C ++ Users Journal, 22 (5), maj 2004
  2. Herb Sutter, David E. Miller, Bjarne Stroustrup Strongly Typed Enums (rewizja 3), lipiec 2007
Sergey Teplyakov
źródło
Lubię to. Wymusza również utworzenie wystąpienia wyliczenia z prawidłową wartością. Myślę, że operator przypisania i konstruktor kopiujący byłyby przydatne. Również t_ powinno być prywatne. Makra, bez których mogę się obejść.
jmucchiello
Ja też to lubię. Dzięki za referencje.
anio,
1
Powiedziałeś: „także jest znacznie bardziej bezpieczny dla typów (nie możesz przypisać, a nawet porównać dwóch różnych wyliczeń ...”) . Dlaczego uważasz, że to dobra funkcja? Myślę, że if(c2 == Color::Red )jest rozsądna i musi się skompilować, ale w twoim przykładzie jest to nie. Ten sam argument dla przypisania również!
Nawaz
3
@Nawaz c2jest innego typu ( Color2), więc jak myślisz, dlaczego c2 == Color::Redprzypisania powinny się kompilować? A jeśli Color::Redjest 1, a Color2::Redto 2? Powinien Color::Red == Color2::Redocenić truelub false? Jeśli pomieszasz niezabezpieczone typy wyliczające, będziesz się źle bawić.
Victor K
2
Dlaczego nie jest Type t_; prywatny?
Zingam
7

Ogólnie rzecz biorąc, zawsze umieszczam wyliczenia w pliku struct. Widziałem kilka wskazówek, w tym „prefiksowanie”.

enum Color
{
  Clr_Red,
  Clr_Yellow,
  Clr_Blue,
};

Zawsze uważałem, że wygląda to bardziej jak Cwytyczne niż C++jedne (na przykład ze względu na skrót, a także ze względu na przestrzenie nazw w C++).

Aby ograniczyć zakres, mamy teraz dwie alternatywy:

  • przestrzenie nazw
  • struktury / klasy

Osobiście zwykle używam a, structponieważ może być używany jako parametr do programowania szablonów, podczas gdy przestrzenią nazw nie można manipulować.

Przykłady manipulacji obejmują:

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

która zwraca liczbę elementów enum wewnątrz struktury T:)

Matthieu M.
źródło
3

Jeśli tworzysz bibliotekę kodu, użyłbym przestrzeni nazw. Jednak w tej przestrzeni nazw nadal można mieć tylko jedno wyliczenie koloru. Jeśli potrzebujesz wyliczenia, które może używać wspólnej nazwy, ale może mieć różne stałe dla różnych klas, użyj swojego podejścia.

Harvey
źródło