Ein enum X : int
(C #) oder enum class X : int
(C ++ 11) ist ein Typ, dessen verstecktes inneres Feld int
einen beliebigen Wert enthalten kann. Außerdem sind eine Reihe vordefinierter Konstanten von X
in der Enumeration definiert. Es ist möglich, die Enumeration auf ihren ganzzahligen Wert zu setzen und umgekehrt. Dies gilt sowohl für C # als auch für C ++ 11.
In C # werden Aufzählungen nicht nur zum Speichern einzelner Werte verwendet, sondern auch zum Speichern bitweiser Kombinationen von Flags, wie von Microsoft empfohlen . Solche Aufzählungen sind (normalerweise, aber nicht unbedingt) mit dem [Flags]
Attribut versehen. Um das Leben der Entwickler zu vereinfachen, sind die bitweisen Operatoren (OR, AND, etc ...) überladen, sodass Sie auf einfache Weise Folgendes tun können (C #):
void M(NumericType flags);
M(NumericType.Sign | NumericType.ZeroPadding);
Ich bin ein erfahrener C # -Entwickler, programmiere aber erst seit ein paar Tagen C ++ und bin mit den C ++ - Konventionen nicht vertraut. Ich beabsichtige, eine C ++ 11-Enumeration genauso zu verwenden, wie ich es in C # getan habe. In C ++ 11 sind die bitweisen Operatoren für bereichsspezifische Enums nicht überladen, daher wollte ich sie überladen .
Dies löste eine Debatte aus, und die Meinungen scheinen zwischen drei Optionen zu variieren:
Eine Variable vom Typ enum wird verwendet, um das Bitfeld zu halten, ähnlich wie in C #:
void M(NumericType flags); // With operator overloading: M(NumericType::Sign | NumericType::ZeroPadding); // Without operator overloading: M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));
Dies würde jedoch der stark typisierten Enum-Philosophie von C ++ 11 widersprechen.
Verwenden Sie eine einfache Ganzzahl, wenn Sie eine bitweise Kombination von Enums speichern möchten:
void M(int flags); M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));
Dies würde jedoch alles auf eine reduzieren
int
, sodass Sie keine Ahnung haben, welchen Typ Sie in die Methode einfügen sollen.Schreiben Sie eine separate Klasse, die Operatoren überlädt und die bitweisen Flags in einem versteckten Ganzzahlfeld enthält:
class NumericTypeFlags { unsigned flags_; public: NumericTypeFlags () : flags_(0) {} NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {} //...define BITWISE test/set operations }; void M(NumericTypeFlags flags); M(NumericType::Sign | NumericType::ZeroPadding);
( vollständiger Code von user315052 )
Dann haben Sie jedoch weder IntelliSense noch eine andere Unterstützung, um auf die möglichen Werte hinzuweisen.
Ich weiß, dass dies eine subjektive Frage ist , aber: Welchen Ansatz soll ich verwenden? Welcher Ansatz ist in C ++ am weitesten verbreitet? Welchen Ansatz verwenden Sie beim Umgang mit Bitfeldern und warum ?
Da alle drei Ansätze funktionieren, suche ich natürlich nach sachlichen und technischen Gründen, allgemein anerkannten Konventionen und nicht nur nach persönlichen Vorlieben.
Aufgrund meines C # -Hintergrunds tendiere ich beispielsweise dazu, Ansatz 1 in C ++ zu wählen. Dies hat den zusätzlichen Vorteil, dass meine Entwicklungsumgebung mich auf die möglichen Werte hinweisen kann, und bei überladenen Enum-Operatoren ist dies einfach zu schreiben und zu verstehen und ziemlich sauber. Und die Methodensignatur zeigt deutlich, welchen Wert sie erwartet. Aber die meisten Leute hier stimmen mir nicht zu, wahrscheinlich aus gutem Grund.
quelle
enum E { A = 1, B = 2, C = 4, };
, der Bereich ist0..7
(3 Bit). Somit garantiert der C ++ - Standard explizit, dass # 1 immer eine praktikable Option ist. [Standardmäßig wirdenum class
,enum class : int
sofern nicht anders angegeben, immer ein fester zugrunde liegender Typ verwendet.])Antworten:
Am einfachsten ist es, den Bediener selbst überladen zu lassen. Ich denke an die Erstellung eines Makros, um die grundlegenden Überladungen pro Typ zu erweitern.
(Beachten Sie, dass
type_traits
es sich um einen C ++ 11-Header undstd::underlying_type_t
eine C ++ 14-Funktion handelt.)quelle
static_cast<T>
für die Eingabe, aber das Ergebnis wird hier im C-Stil umgewandelt?SBJFrameDrag
in einer Klasse definiert ist und der|
Operator später in den Definitionen derselben Klasse verwendet wird, wie würden Sie den Operator so definieren, dass er in der Klasse verwendet werden kann?In der Vergangenheit hätte ich immer die alte (schwach typisierte) Aufzählung verwendet, um die Bitkonstanten zu benennen, und nur die Speicherklasse explizit verwendet, um das resultierende Flag zu speichern. Hier müsste ich sicherstellen, dass meine Aufzählungen in den Speichertyp passen, und die Zuordnung zwischen dem Feld und den zugehörigen Konstanten verfolgen.
Ich mag die Idee stark typisierter Aufzählungen, aber ich bin nicht wirklich mit der Idee einverstanden, dass Variablen des Aufzählungstyps Werte enthalten können, die nicht zu den Konstanten dieser Aufzählung gehören.
ZB unter der Annahme, das bitweise oder wurde überlastet:
Für Ihre dritte Option benötigen Sie ein Boilerplate, um den Speichertyp der Aufzählung zu extrahieren. Angenommen, wir möchten einen nicht signierten zugrunde liegenden Typ erzwingen (mit etwas mehr Code können wir auch signierte Typen verarbeiten):
Dadurch erhalten Sie immer noch kein IntelliSense oder keine automatische Vervollständigung, aber die Erkennung des Speichertyps ist weniger hässlich als ursprünglich erwartet.
Jetzt habe ich eine Alternative gefunden: Sie können den Speichertyp für eine schwach typisierte Aufzählung angeben. Es hat sogar die gleiche Syntax wie in C #
Da es schwach typisiert ist und implizit in / von int konvertiert wird (oder welchen Speichertyp Sie auch immer wählen), ist es weniger seltsam, Werte zu haben, die nicht mit den aufgezählten Konstanten übereinstimmen.
Der Nachteil ist, dass dies als "Übergang" beschrieben wird ...
NB. Diese Variante fügt ihre aufgezählten Konstanten sowohl dem verschachtelten als auch dem umschließenden Bereich hinzu. Sie können dies jedoch mit einem Namespace umgehen:
quelle
Sie können typsichere Enum-Flags in C ++ 11 definieren, indem Sie verwenden
std::enable_if
. Dies ist eine rudimentäre Implementierung, bei der möglicherweise einige Dinge fehlen:Beachten Sie,number_of_bits
dass dies vom Compiler leider nicht ausgefüllt werden kann, da C ++ keine Möglichkeit hat, die möglichen Werte einer Aufzählung zu überprüfen.Edit: Eigentlich stehe ich korrigiert da, es ist möglich den Compiler
number_of_bits
für dich auszufüllen .Beachten Sie, dass dies einen nicht kontinuierlichen Enum-Wertebereich (ineffizient) verarbeiten kann. Sagen wir einfach, es ist keine gute Idee, das Obige mit einer Aufzählung wie dieser zu verwenden, da sonst der Wahnsinn entsteht:
Alles in allem ist dies jedoch eine durchaus brauchbare Lösung. Benötigt kein benutzerseitiges Bitfiddling, ist typsicher und innerhalb seiner Grenzen, so effizient wie es nur geht (ich verlasse mich hier stark auf die
std::bitset
Implementierungsqualität;)
).quelle
ich
HassIch verabscheue Makros in meinem C ++ 14 genauso sehr wie den nächsten, aber ich habe mich daran gemacht, sie überall zu verwenden, und das auch recht großzügig:Gebrauch so einfach machen wie
Und wie sie sagen, ist der Beweis im Pudding:
Sie können die Definition einzelner Operatoren jederzeit aufheben. Meiner Meinung nach eignet sich C / C ++ jedoch für die Verknüpfung mit Konzepten und Streams auf niedriger Ebene, und Sie können diese bitweisen Operatoren aus meinen kalten, toten Händen hebeln und ich werde dich mit all den unheiligen Makros und Sprüchen bekämpfen, die ich beschwören kann, um sie zu behalten.
quelle
std::enable_if
mitstd::is_enum
Ihre freien Operatorüberladungen darauf beschränken können, nur mit Aufzählungstypen zu arbeiten. Ich habe auch Vergleichsoperatoren (usingstd::underlying_type
) und den logischen Nicht-Operator hinzugefügt , um die Lücke weiter zu schließen, ohne die starke Typisierung zu verlieren. Das einzige , was ich nicht ist implizite Konvertierung in bool mithalten kann, aberflags != 0
und!flags
ist für mich ausreichend.Normalerweise definieren Sie eine Reihe von Ganzzahlwerten, die den gesetzten Einzelbit-Binärzahlen entsprechen, und addieren sie dann. Dies ist die Art und Weise, wie C-Programmierer dies normalerweise tun.
Also müssten Sie (mit dem Bitverschiebungsoperator die Werte einstellen, z. B. 1 << 2 ist das Gleiche wie Binär 100)
usw
In C ++ haben Sie mehr Optionen. Definieren Sie einen neuen Typ, der ein int ist (verwenden Sie typedef ), und setzen Sie die Werte wie oben beschrieben. oder definieren Sie ein Bitfeld oder einen Vektor von Bools . Die letzten beiden sind sehr platzsparend und für den Umgang mit Flags viel sinnvoller. Ein Bitfeld hat den Vorteil, dass Sie die Typüberprüfung (und damit die Intellisense-Funktion) durchführen können.
Ich würde sagen (natürlich subjektiv), dass ein C ++ - Programmierer ein Bitfeld für Ihr Problem verwenden sollte, aber ich neige dazu, den von C-Programmen häufig in C ++ -Programmen verwendeten #define-Ansatz zu sehen.
Ich nehme an, das Bitfeld ist der Aufzählung von C # am nächsten. Deshalb ist es seltsam, dass C # versucht hat, eine Aufzählung als Bitfeldtyp zu überladen - eine Aufzählung sollte eigentlich ein "Single-Select" -Typ sein.
quelle
0b0100
), sodass das1 << n
Format veraltet ist.Ein kurzes Beispiel für Enum-Flags unten ähnelt C #.
Über den Ansatz, meiner Meinung nach: weniger Code, weniger Bugs, besserer Code.
ENUM_FLAGS (T) ist ein in enum_flags.h definiertes Makro (weniger als 100 Zeilen, frei und ohne Einschränkungen verwendbar).
quelle
::type
dort gefehlt . BehobenEs gibt noch einen anderen Weg, die Katze zu häuten:
Anstatt die Bit-Operatoren zu überladen, ziehen es einige vielleicht vor, nur einen 4-Liner hinzuzufügen, um diese unangenehme Einschränkung von Aufzählungen zu umgehen:
Zugegeben, Sie müssen das
ut_cast()
Ding jedes Mal tippen , aber auf der anderen Seite liefert dies besser lesbaren Code im gleichen Sinne wie die Verwendungstatic_cast<>()
im Vergleich zu impliziter Typkonvertierung oderoperator uint16_t()
Art von Dingen.Und seien wir ehrlich, die Verwendung von Typ
Foo
wie im obigen Code birgt seine Gefahren:An einer anderen Stelle könnte jemand eine Switch-Case-Operation über eine Variable ausführen
foo
und nicht erwarten, dass sie mehr als einen Wert enthält ...Wenn Sie also den Code damit
ut_cast()
verunreinigen, werden Sie darauf aufmerksam gemacht, dass etwas faul ist.quelle