Jakich typów Enum w C++ używasz?

Powszechnie wiadomo, że wbudowane enemy w C++ nie są typami. Zastanawiałem się, które klasy implementujące typy enum są tam używane... Sam używam następującego "roweru", ale jest on nieco gadatliwy i ograniczony: {]}

Typesafeenum.h:

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

Typesafeenum.cpp:

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

Użycie:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

Dodatek: Myślę, że powinienem być bardziej szczegółowy co do wymagań. Postaram się je podsumować:

Priorytet 1: Ustawienie zmiennej enum na nieprawidłową wartość powinno być niemożliwe (błąd w czasie kompilacji) bez wyjątków.

Priorytet 2: Konwersja wartości enum na / z int powinna być możliwa za pomocą pojedynczego jawnego wywołania funkcji / metody.

Priorytet 3: jak najbardziej kompaktowa, elegancka i wygodna deklaracja i użytkowanie

Priorytet 4: Konwersja wartości enum na i z łańcuchów.

Priorytet 5: (miło mieć) możliwość iteracji nad wartościami enum.

Author: Sebastian Mach, 2008-10-20

11 answers

Obecnie bawię się z Boostem.Enum proposal from the Boost Vault (filename enum_rev4.6.zip). Chociaż nigdy nie został oficjalnie zgłoszony do włączenia do Boost, jest użyteczny w obecnej postaci. (Brak dokumentacji, ale jest ona uzupełniona przez jasny kod źródłowy i dobre testy.)

/ Align = "left" / Enum pozwala zadeklarować enum w następujący sposób:
BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

I niech automatycznie rozszerzy się do tego:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};
Spełnia wszystkie pięć priorytetów, które wymieniasz.
 42
Author: Josh Kelley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-03 19:20:00

Fajna metoda kompromisu jest taka:

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

To nie jest typesafe w tym samym sensie, co Twoja wersja, ale użycie jest ładniejsze niż standardowe Liczby i nadal możesz skorzystać z konwersji liczb całkowitych, gdy tego potrzebujesz.

 18
Author: Charlie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-20 05:09:03

Używam C++0x typów . Używam kilku pomocniczych szablonów / makr, które zapewniają funkcjonalność ciągów to / from.

enum class Result { Ok, Cancel};
 12
Author: Roddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-13 14:09:42

Ja nie. zdecydowanie za dużo kosztów na małe korzyści. Bardzo przydatnym narzędziem jest również możliwość wyliczania różnych typów danych do serializacji. Nigdy nie widziałem instancji, w której wyliczenie "Type safe" byłoby warte narzutu i złożoności, w której C++ oferuje już wystarczająco dobrą implementację.

 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-20 05:04:33

Moim zdaniem wymyślasz problem, a potem dopasowujesz rozwiązanie do niego. Nie widzę potrzeby tworzenia rozbudowanych ram dla wyliczania wartości. Jeśli jesteś dedykowany, aby twoje wartości były tylko członkami określonego zestawu, możesz zhakować wariant unikalnego typu danych zestawu.

 2
Author: Paul Nathan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-20 15:09:24

Ja osobiście używam zaadaptowanej wersji idiomu typesafe enum . Nie zawiera wszystkich pięciu "wymagań", które podałeś w swojej edycji, ale i tak zdecydowanie nie zgadzam się z niektórymi z nich. Na przykład, nie widzę, jak Prio # 4 (Konwersja wartości na łańcuchy znaków) ma coś wspólnego z bezpieczeństwem typu. W większości przypadków reprezentacja łańcuchów poszczególnych wartości powinna być oddzielona od definicji typu i tak (pomyśl i18n z prostego powodu dlaczego). Prio # 5(iteratio, jest to jedna z najładniejszych rzeczy, które chciałbym zobaczyć naturalnie dzieje się w enumach, więc czułem się smutny, że pojawia się jako "opcjonalne" w twojej prośbie, ale wydaje się, że jest lepiej zaadresowane przez oddzielny system iteracji, taki jak begin/end funkcje lub enum_iterator, co sprawia, że działają bezproblemowo z STL i C++11 foreach.

OTOH ten prosty idiom ładnie zapewnia Prio # 3 Prio#1 dzięki temu, że w większości owija enum s z większym typem informacje. Nie wspominając o tym, że jest to bardzo proste rozwiązanie, które w większości nie wymaga żadnych zewnętrznych nagłówków zależności, więc jest dość łatwe do przenoszenia. Ma również tę zaletę, że wyliczenia mają zasięg a-la-C++11: {]}

// This doesn't compile, and if it did it wouldn't work anyway
enum colors { salmon, .... };
enum fishes { salmon, .... };

// This, however, works seamlessly.
struct colors_def { enum type { salmon, .... }; };
struct fishes_def { enum type { salmon, .... }; };

typedef typesafe_enum<colors_def> colors;
typedef typesafe_enum<fishes_def> fishes;

Jedyną" dziurą", jaką zapewnia rozwiązanie, jest to, że nie eliminuje faktu, że nie zapobiega bezpośrednio porównywaniu enumS różnych typów (lub enum i int), ponieważ gdy używasz wartości bezpośrednio, wymuszasz implicit konwersja do int:

if (colors::salmon == fishes::salmon) { .../* Ooops! */... }

Ale jak dotąd odkryłem, że takie problemy można rozwiązać po prostu oferując lepsze porównanie do kompilatora - na przykład jawnie podając operator, który porównuje dowolne dwa różne typy enum, a następnie zmuszając go do niepowodzenia: {]}

// I'm using backports of C++11 utilities like static_assert and enable_if
template <typename Enum1, typename Enum2>
typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool >
::type operator== (Enum1, Enum2) {
    static_assert (false, "Comparing enumerations of different types!");
}

Choć do tej pory nie wydaje się łamać kodu, a robi to, aby wyraźnie poradzić sobie z konkretnym problemem bez robienia czegoś innego, nie jestem pewien, czy takie coś jest rzeczą, którą "powinien" zrobić (podejrzewam, że będzie przeszkadzał enum s już biorącym udział w operatorach konwersji zadeklarowanych gdzie indziej; chętnie otrzymałbym komentarz na ten temat).

Połączenie tego z powyższym idiomem typesafe daje coś, co jest stosunkowo bliskie C++11 enum class w człowiekowości (czytelności i łatwości utrzymania) bez konieczności robienia czegokolwiek zbyt niejasnego. I muszę przyznać, że to było zabawne, nigdy nie myślałem, aby właściwie zapytać kompilatora, czy miałem do czynienia z enums, czy nie...

 2
Author: Luis Machuca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:07:19

Myślę, że Java enum będzie dobrym modelem do naśladowania. Zasadniczo, forma Java wyglądałaby tak:

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Interesujące w podejściu Java jest to, że OK i CANCEL są niezmiennymi, pojedynczymi instancjami Result (z metodami, które widzisz). Nie można tworzyć kolejnych instancji Result. Ponieważ są singletonami, możesz porównać według wskaźnika/odniesienia - - - bardzo przydatne. :-)

ETA: w Javie zamiast ręcznie wykonywać bitmaski, zamiast tego używasz EnumSet do określa zestaw bitów (implementuje interfejs Set i działa jak zestawy - - - ale zaimplementowane przy użyciu bitmask). O wiele bardziej czytelne niż ręcznie pisana manipulacja maską bitową!

 1
Author: Chris Jester-Young,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-20 05:08:13

Odpowiedziałem na to tutaj, na inny temat. Jest to inny styl podejścia, który pozwala na większość tej samej funkcjonalności bez konieczności modyfikacji oryginalnej definicji enum (i w konsekwencji pozwala na użycie w przypadkach, gdy nie zdefiniujesz enum). Umożliwia również sprawdzanie zakresu działania.

Minusem mojego podejścia jest to, że nie wymusza ono programowo sprzężenia między klasą enum a klasą pomocniczą, więc muszą być aktualizowane w równolegle. U mnie działa, ale YMMV.

 1
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:16:28

Obecnie piszę własną bibliotekę typesafe enum na https://bitbucket.org/chopsii/typesafe-enums

Nie jestem najbardziej doświadczonym programistą C++, ale piszę to ze względu na braki w enumach boost vault.

Możesz je sprawdzić i używać samodzielnie, ale mają pewne (miejmy nadzieję drobne) problemy z użytecznością i prawdopodobnie nie są w ogóle wieloplatformowe.

Proszę przyczynić się, jeśli chcesz. To mój pierwszy open source zobowiązanie.

 0
Author: Lynden Shields,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-16 08:51:14

Użyj boost::variant!

Po wypróbowaniu wielu powyższych pomysłów i znalezieniu ich braku trafiłem na takie proste podejście:]}
#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> BC;

void ab(const AB & e)
{
  if(isA(e))
    std::cerr << "A!" << std::endl;
  if(isB(e))
    std::cerr << "B!" << std::endl;
  // ERROR:
  // if(isC(e))
  //   std::cerr << "C!" << std::endl;

  // ERROR:
  // if(e == 0)
  //   std::cerr << "B!" << std::endl;
}

void bc(const BC & e)
{
  // ERROR:
  // if(isA(e))
  //   std::cerr << "A!" << std::endl;

  if(isB(e))
    std::cerr << "B!" << std::endl;
  if(isC(e))
    std::cerr << "C!" << std::endl;
}

int main() {
  AB a;
  a = A;
  AB b;
  b = B;
  ab(a);
  ab(b);
  ab(A);
  ab(B);
  // ab(C); // ERROR
  // bc(A); // ERROR
  bc(B);
  bc(C);
}

Prawdopodobnie możesz wymyślić makro do wygenerowania kotła. (Daj mi znać, jeśli to zrobisz.)

W przeciwieństwie do innych podejść ten jest tak naprawdę bezpieczny dla typu i działa ze starym C++. Możesz nawet tworzyć fajne typy, takie jak boost::variant<int, A_t, B_t, boost::none>, na przykład, aby reprezentować wartość, która może być a, B, liczbą całkowitą lub niczym, która jest prawie poziomami typu Haskell98 bezpieczeństwo.

Minusy do zapamiętania:

    W przeciwieństwie do poprzednich wersji, nie jest to możliwe, ale jest to możliwe tylko w przypadku wersji 1.33.]}
  • wpływa na czas kompilacji
  • obłąkane komunikaty o błędach -- ale to C++ dla Ciebie

Update

Tutaj, dla Twojej wygody jest Twoja biblioteka typesafe-enum. Wklej ten nagłówek:

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#define ITEM(NAME, VAL) \
struct NAME##_t { \
  std::string toStr() const { return std::string( #NAME ); } \
  int toInt() const { return VAL; } \
}; \
static const NAME##_t NAME = NAME##_t(); \
template <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toInt_visitor(), a);
}

#define ENUM(...) \
typedef boost::variant<__VA_ARGS__>
#endif

I używaj go jak:

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

Zauważ, że musisz powiedzieć A_t zamiast A w makrze ENUM, które niszczy część magii. No cóż. Zauważ również, że istnieje teraz funkcja toStr i funkcja toInt, która spełnia wymagania Ops prostej konwersji na ciągi znaków i ints. Wymóg, którego nie mogę rozgryźć, to sposób na iterację przedmiotów. Daj mi znać, jeśli wiesz, jak napisać coś takiego.

 0
Author: Michael Fox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-06 21:53:47

Nie wiem, czy ten post jest za późno, ale jest artykuł o GameDev.net który spełnia wszystkie oprócz 5-tego punktu (Możliwość iteracji nad wyliczeniami): http://www.gamedev.net/reference/snippets/features/cppstringizing/

Metoda opisana w artykule umożliwia obsługę konwersji łańcuchów dla istniejących wyliczeń bez zmiany ich kodu. Jeśli chcesz tylko wsparcia dla nowych wyliczeń, wybrałbym Boost.Enum (wspomniany wyżej).

 -2
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-07 23:52:15