Frage

Es ist allgemein bekannt, dass integrierte in Aufzählungen in C ++ sind nicht typsicher. Ich habe mich gefragt, welche Klassen typsichere Aufzählungen Implementierung werden dort verwendet, aus ... Ich selbst benutze das folgende „Fahrrad“, aber es ist etwas weitschweifig und begrenzt:

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

Verbrauch:

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;

Addition: Ich denke, ich sollte über die Anforderungen spezifischer gewesen. Ich werde versuchen, sie zusammenfassen:

Priorität. 1: ein ENUM-Variable auf einen ungültigen Wert einstellen soll (ein Fehler bei der Kompilierung) ohne Ausnahmen unmöglich sein

Priorität. 2: Konvertieren einen ENUM-Wertes zu / von einem int sollte mit einem einzigen expliziten Funktion / Methodenaufruf möglich sein

Priorität 3: Als kompakte, elegante und bequeme Erklärung und Nutzung wie möglich

Priorität. 4: Konvertieren von ENUM-Werten zu und von Strings

Priorität 5: (Nice to have) Möglichkeit über ENUM-Werte iterieren

.
War es hilfreich?

Lösung

ich zur Zeit das Spiel mit dem Boost.Enum Vorschlag der Vault Boost (Dateiname enum_rev4.6.zip). Obwohl es nie offiziell für die Aufnahme in-Boost vorgelegt wurde, ist es verwendbar, wie sie ist. (Dokumentation fehlt, ist aber für die durch klare Quellcode und gute Tests aus.)

Boost.Enum können Sie eine Enumeration wie folgt erklären:

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")
)

Und haben sie automatisch auf diese erweitern:

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

Es erfüllt alle fünf der Prioritäten, die Sie Liste.

Andere Tipps

Eine schöne Kompromiss Methode ist folgende:

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

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

Es ist nicht im gleichen Sinne typsicher, dass Ihre Version ist, aber die Nutzung ist schöner als Standard Aufzählungen, und Sie können immer noch die Vorteile von Integer-Konvertierung nehmen, wenn Sie es brauchen.

Ich verwende C ++ 0x typsichere Aufzählungen . Ich benutze Helfer template / Makros, die zu / von String-Funktionalität bereitstellen.

enum class Result { Ok, Cancel};

ich nicht. Viel zu viel Aufwand für wenig Nutzen. Auch ist in der Lage, die auf dem Kasten Aufzählungen zu verschiedenen Datentypen für die Serialisierung ein sehr nützliches Werkzeug. Ich habe noch nie eine Instanz gesehen, wo eine „Typen sicher“ Aufzählung den Aufwand und die Komplexität wäre wert, wo C ++ bereits gut genug Implementierung bietet.

Meine Meinung ist, dass Sie ein Problem sind zu erfinden und dann eine Lösung auf ihn aufsetzen. Ich sehe keine Notwendigkeit, einen aufwendigen Rahmen für eine Aufzählung von Werten zu tun. Wenn Sie gewidmet , die nur Ihre Werte Mitglieder einer bestimmten Gruppe sein, könnten Sie eine Variante eines eindeutigen Satz Datentyp zerhacken.

Ich persönlich eine angepasste Version des typsichere Enum-Idiom . Es bietet nicht all fünf „Anforderungen“, die Sie in Ihrem bearbeiten gesagt haben, aber ich stimme nicht stark mit einigen von ihnen sowieso. Zum Beispiel sehe ich nicht, wie Prio # 4 (Umwandlung von Werten in Strings) etwas mit Typ-Sicherheit zu tun hat. Die meiste Zeit String-Darstellung der einzelnen Werte sollten sowieso (man denke i18n für einen einfachen Grund, warum) von der Definition des Typs getrennt sein. Prio # 5 (iteratio, die optional ist) ist eines der schönsten Dinge, Ich mag, Sie sehen natürlich in Aufzählungen passiert, so fühlte ich mich traurig, dass es scheint, als „optional“ in Ihrer Anfrage, aber es scheint, ist es besser, so über ein separate Iteration System adressierte als begin / end Funktionen oder eine enum_iterator, die sie arbeiten nahtlos mit STL und C ++ 11 foreach.

macht

OTOH dieses einfache Idiom bietet schön Prio # 3 # Prio 1 dank der Tatsache, dass es meist nur hüllt enums mit mehr Typinformationen. Nicht zu erwähnen, es eine sehr einfache Lösung ist, die zum größten Teil keine externe Abhängigkeit Header benötigt, so ist es ziemlich einfach zu tragen. Es hat auch den Vorteil, Aufzählungen machen 11 a-la-C ++ scoped:

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

Das einzige „Loch“ diese Lösung bietet, ist, dass es nicht die Tatsache anspricht, dass es nicht enums verschiedenen Typen nicht verhindern (oder eine enum und einen int) aus direkt verglichen werden, weil, wenn Sie Werte direkt verwenden Sie zwingen, die implizite Konvertierung in int:

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

Aber bisher habe ich festgestellt, solche Probleme können durch einfaches bietet einen besseren Vergleich zu den Compiler gelöst werden - beispielsweise explizit einen Operator bereitstellt, die alle zwei verschiedene enum Typen vergleicht, dann zwingt sie zum Scheitern verurteilt:

// 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!");
}

Auch wenn es scheint nicht, den Code zu brechen, so weit, und es tut sich explizit mit dem spezifischen Problem zu behandeln, ohne etwas anderes zu tun, ich bin nicht sicher, ob es so etwas ist eine Sache, eine „ sollte “tun (ich vermute, dass es mit enums bereits der Teilnahme an Umwandlung Betreibern an anderer Stelle erklärt stören, ich Kommentar zu diesem gerne erhalten würde).

In Kombination mit dem oben typsichere Idiom gibt etwas, das relativ nahe an C ++ 11 enum class in humanibility ist (Lesbarkeit und Wartbarkeit), ohne etwas zu dunkel zu tun. Und ich muss zugeben, es war lustig zu machen, hatte ich eigentlich nie gedacht, fragen der Compiler, wenn ich mit enums oder nicht zu tun hatte ...

Ich denke, das Java enum ein gutes Modell sein würde, zu folgen. Im Wesentlichen wie dies die Java Form aussehen:

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

    private final String name;

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

    public String getName() {
        return name;
    }
}

Was ist mit dem Java-Ansatz interessant ist, dass OK und CANCEL sind unveränderlich, Singleton Instanzen von Result (mit den Methoden, die Sie sehen). Sie können keine weiteren Instanzen von Result erstellen. Da sie Singletons sind, können Sie durch Zeiger / Referenz --- sehr praktisch vergleichen. : -)

ETA: In Java statt Bitmasken von Hand tun, stattdessen verwenden Sie ein EnumSet ein Bit gesetzt, um anzugeben (es implementiert die Set Schnittstelle und funktioniert wie Sätze --- aber implementiert Bitmasken). Viel besser lesbar als handgeschriebene bitmask Manipulation!

Ich habe eine Antwort auf diese hier , zu einem anderen Thema. Es ist eine andere Art von Ansatz, ohne dass Änderungen an der ursprünglichen Enumerationsdefinition die meisten der gleichen Funktionalität ermöglicht (und folglich in den Fällen erlaubt Nutzung in denen Sie nicht die Enum definieren). Es ermöglicht auch Laufzeitbereichsprüfung.

Der Nachteil meines Ansatzes ist, dass es nicht programmatisch die Kopplung zwischen dem ENUM und der Hilfsklasse erzwingt, so dass sie parallel aktualisiert werden. Es funktioniert für mich, aber YMMV.

Ich bin derzeit schreibe meine eigene typsichere Enum-Bibliothek unter https://bitbucket.org/chopsii/typesafe- Aufzählungen

Ich bin nicht die erfahrenste C ++ Entwickler immer, aber ich schreibe dies aufgrund der Mängel der BOOST Gewölbe Aufzählungen.

Fühlen Sie sich frei, es auszuprobieren und selbst verwenden, aber sie haben einige (hoffentlich minor) Usability-Probleme und sind wahrscheinlich nicht alle Cross-Plattform.

Bitte tragen, wenn Sie wollen. Dies ist meine ersten Open-Source-Unternehmen.

Verwenden boost::variant!

Nachdem viele der oben genannten Ideen versuchen, und die Suche nach ihnen fehlt ich auf diesem einfachen Ansatz getroffen:

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

Sie können sich wahrscheinlich mit einem Makro kommen, um den vorformulierten zu erzeugen. (Lassen Sie mich wissen, wenn Sie tun.)

Im Gegensatz zu anderen Ansätzen dieser ist typsicher tatsächlich und arbeitet mit alten C ++. Sie können sogar coole Typen wie boost::variant<int, A_t, B_t, boost::none>, zum Beispiel machen, um einen Wert zu repräsentieren, das mit A, B, einer ganzen Zahl oder nichts, was Haskell98 Ebene der Typsicherheit ist fast sein könnte.

Die Nachteile bewusst sein:

  • at-dest mit altem boost - ich bin auf einem System mit Boost 1.33 - Sie sind auf 20 Artikel im Variante beschränkt; gibt es eine Behelfslösung jedoch
  • Zeit wirkt kompilieren
  • verrückt Fehlermeldungen - aber das ist C ++ für Sie

Update

Hier für Ihre Bequemlichkeit ist Ihre typsicher-Enumeration „Bibliothek“. Fügen Sie diesen Header:

#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

Und verwenden Sie es mögen:

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

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

Beachten Sie A_t statt A im ENUM Makro zu sagen haben, die etwas von der Magie zerstört. Naja. Beachten Sie auch, gibt es jetzt eine toStr Funktion und eine toInt Funktion OPs Erfordernis der einfachen Umwandlung in Strings und Ints zu erfüllen. Die Forderung kann ich nicht herausfinden, ist eine Möglichkeit, über die Elemente iterieren. Lassen Sie mich wissen, wenn Sie wissen, wie man schreibt so eine Sache.

Nicht sicher, ob dieser Beitrag zu spät ist, aber es ist ein Artikel auf GameDev.net, die alle bis auf den fünften Punkt erfüllt (die Fähigkeit, über Enumeratoren iterieren): http://www.gamedev.net/reference/snippets/features/cppstringizing/

Das von dem Artikel beschriebene Verfahren String-Konvertierung Unterstützung ermöglicht vorhandenen Aufzählungen ohne ihren Code zu verändern. Wenn Sie nur wenn die Unterstützung für neue Aufzählungen wollen, würde ich mit Boost.Enum gehen (siehe oben).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top