Veroderung von enum-Werten
-
camper schrieb:
Ich fände es fragwürdig, wenn der numerische Wert jemals im Programm relevant werden sollte...
Muss ja nicht.
foo blubb = do_something(); switch(blubb) { ... }
reicht schon.
Eine Aufzählung ist eine Aufzählung. Wenn da in eine Variable - wie auch immer - Werte reinkommen, die in der Aufzählung nicht vorhanden sind, dann ist das bestenfalls ein Hack. Wenn C++ Bitmasken unterstützen will, warum dann nicht richtig? Diese "Lösung" ist doch wieder dem übertriebenen bloß-keine-reservierte-Bezeichner-einführen-Wahn geschuldet. Wer das gut findet, der leidet am Stockholm-Syndrom.
Edit: was du da gepostet hast, sieht ja fast aus wie der
KotCode von Nathan. Hat sein Compiler unrecht, wenn er warnt?
-
#include <iostream> enum NIBBLEBITS {BIT1 = 1, BIT2 = 2, BIT3 = 4, BIT4 = 8}; namespace { struct Ored { int mret; explicit Ored(NIBBLEBITS a, NIBBLEBITS b) : mret(a|b) {} }; } Ored operator|(NIBBLEBITS n1, NIBBLEBITS n2) { return Ored(n1, n2); } void testfunc(NIBBLEBITS stuff) { std::cout << stuff << std::endl; } void testfunc(Ored stuff) { std::cout << stuff.mret << std::endl; } int main() { testfunc(BIT1); testfunc(BIT1|BIT2); testfunc(1); //geht nat. nicht... }
Wie wärs damit? Jetzt die Klasse und den anonymen Namensraum in einen andere Übersetzungseinheit und bei belieben natürlich noch ergänzen und fertig...
-
Hallo,
als Threadstarter melde ich mich nochmal.
Danke an alle! Habe wieder was dazu gelernt.
Und wie camper schon schrieb, bilde ich mit enums Flags ab. Dafür sind enums auf jedenfall besser als ints. (siehe Beitrag von camper)
Gruss Ludger.
-
dd++ schrieb:
Vorschlag:
+1
Wieso die Funktion nur auf das eine Enum begrenzen? Kann man doch auch wunderbar für andere numerische Werte benutzen.
#include <iostream> enum NIBBLEBIT {BIT0 = 1, BIT1 = 2, BIT2 = 4, BIT3 = 8}; void bitProof(int value) { if ((value & BIT0) == BIT0) std::cout << "Bit 0 ist gesetzt" << std::endl; if ((value & BIT1) == BIT1) std::cout << "Bit 1 ist gesetzt" << std::endl; if ((value & BIT2) == BIT2) std::cout << "Bit 2 ist gesetzt" << std::endl; if ((value & BIT3) == BIT3) std::cout << "Bit 3 ist gesetzt" << std::endl; } int main() { bitProof(BIT0); bitProof(BIT0 | BIT1); return 0; }
Enums mag ich ehe nur als reine Aufzählung benutzen. Der Wert dahinter ist meistens egal, bei der BIT Sache drücke ich aber jedesmal ein Auge zu. Ist so doch halt schicker
-
KasF schrieb:
Wieso die Funktion nur auf das eine Enum begrenzen?
Typsicherheit (siehe camper). Man möchte nur Kombinationen der Enumeratoren erlauben.
-
@Captain Obvious
Guck mal die Sache ist doch wirklich denkbar einfach.C++ erlaubt dir enum Typen zu definieren. Per Default haben die keinen operator | und keinen operator & definiert. Wenn man dann
enumA | enumB
schreibt werden die beiden Operanden erstmal in einenint
verwandelt, und dann verodert. Das Ergebnis ist dann einint
, und um wieder nen enum draus zu machen müsste man mitstatic_cast
draufhauen.Da man mit
static_cast
ziemlich viel Unsinn anstellen kann, erübrigt sich mMn. die Frage ob das nun eine weitere Sache ist wo man mitstatic_cast
Unsinn machen kann oder nicht.Wenn man nen enum für Werte braucht deren Veroderung nicht sinnvoll ist, dann ist die Sache damit erledigt. Man definiert eben keine Operatoren für den enum Typ. Und wenn irgendwer meint mit
static_cast
einen (nicht im enum enthaltenen) Wert erzwingen zu müssen, dann ist er hübsch selbst schuld falls dabei Unsinn rauskommt.Wenn man aber typsichere Flags haben will, dann definiert man die besagten Operatoren eben, und der User kann ohne
static_cast
die Werte verodern oder verunden.Wenn man sich nun Sorgen macht ob der zugrundeliegende Typ wohl auch immer gross genug sei, dann kann man ja durchaus was machen. In C++ 03 kann man Dummy-Werte aufnehmen um einen minimalen Wertbereich zu erzwingen. Und in C++ 11 kann man den Typ explizit angeben.
-
KasF schrieb:
Wieso die Funktion nur auf das eine Enum begrenzen? Kann man doch auch wunderbar für andere numerische Werte benutzen.
#include <iostream> enum NIBBLEBIT {BIT0 = 1, BIT1 = 2, BIT2 = 4, BIT3 = 8}; void bitProof(int value) { ...
Achje.
Der Sinn dahinter ist doch gerade dass man NICHT WILL dass diebitProof
Funktion mit einemint
aufgerufen werden kann.Weil man dann...
enum WindowStyle { WindowStyle_None = 0, WindowStyle_Foo = 1 << 0, WindowStyle_Bar = 1 << 1, // ... }; enum FileShareMode { FileShare_None = 0, FileShare_Read = 1 << 0, FileShare_Write = 1 << 1, FileShare_Delete = 1 << 2, }; Window* CreateWindow(int width, int height, int flags); void Fun() { Window* w1 = CreateWindow(WindowStyle_Foo | WindowStyle_Bar, 640, 480); // Leider Unfug Window* w2 = CreateWindow(640, 480, FileShare_Read | FileShare_Write); // Ebenso // ... }
...schreiben kann. Und man will eigentlich nicht dass man das kann.
In dem Beispiel ist noch relativ offensichtlich dass das Quatsch ist. Bei Funktionen die zwei oder mehr Flags Parameter nehmen ist es oft nicht mehr so offensichtlich.
-
hustbaer schrieb:
...
Das ist mir schon bewusst. Ich bezog mich auch nur auf die bitProof-Funktionalität, die er mM nach eigentlich implementieren wollte.
-
Bitflag enums sind durchaus ein gaengiges Mittel. Seit C++11 sowieso.
-
KasF schrieb:
hustbaer schrieb:
...
Das ist mir schon bewusst. Ich bezog mich auch nur auf die bitProof-Funktionalität, die er mM nach eigentlich implementieren wollte.
OK, in dem Fall ... sehe ich das ähnlich.
Ich hatte angenommen dass das bloss ein unglücklich gewähltes Beispiel war.
-
hustbaer schrieb:
@Captain Obvious
Guck mal die Sache ist doch wirklich denkbar einfach.Ist ja nicht so, dass ich nicht verstanden hätte worum es geht. Mein Standpunkt ist einfach, dass eine Variable vom Typ einer Aufzählung keine anderen Werte enthalten sollte als die Aufzählung vorgibt. Das es trotzdem gemacht wird (und offenbar so vorgesehen ist), und man dann der Sache keinen anderen Namen geben konnte als
enum
, finde ich schlecht. Es ist unsauber.
-
Es ist doch eigentlich egal, welchen Wert das enum-Oder-Konstrukt intern hat.
Es soll doch nur verdeutlichen, dass hier zwei Flags kombiniert werden.
Und wenn man das enum sowieso nach int castet, ist es eigentlich auch gar kein enum mehr, sondern ein int.
-
Captain Obvious schrieb:
hustbaer schrieb:
@Captain Obvious
Guck mal die Sache ist doch wirklich denkbar einfach.Ist ja nicht so, dass ich nicht verstanden hätte worum es geht. Mein Standpunkt ist einfach, dass eine Variable vom Typ einer Aufzählung keine anderen Werte enthalten sollte als die Aufzählung vorgibt. Das es trotzdem gemacht wird (und offenbar so vorgesehen ist), und man dann der Sache keinen anderen Namen geben konnte als
enum
, finde ich schlecht. Es ist unsauber.Ja, das Keyword "enum" ist nicht ganz optimal.
Aber soll man deswegen auf eine Möglichkeit verzichten halbwegs typsicher mit Flags zu arbeiten?C++ hat so viele Facetten die ein wenig unsauber sind, wenn man sich an allen stösst und alles meidet was damit zu tun hat bleibt nicht mehr viel übrig. Vermutlich zu wenig um überhaupt noch irgendwelche sinnvollen Programme schreiben zu können.
So sehe ich das zumindest. Und von dem Standpunkt aus ist es vollkommen OK enum für Flags zu verwenden.
-
Ja, das Keyword "enum" ist nicht ganz optimal.
Aber soll man deswegen auf eine Möglichkeit verzichten halbwegs typsicher mit Flags zu arbeiten?C++ hat so viele Facetten die ein wenig unsauber sind, wenn man sich an allen stösst und alles meidet was damit zu tun hat bleibt nicht mehr viel übrig. Vermutlich zu wenig um überhaupt noch irgendwelche sinnvollen Programme schreiben zu können.
So sehe ich das zumindest. Und von dem Standpunkt aus ist es vollkommen OK enum für Flags zu verwenden.
Grundsätzlich stimme ich Dir zu, aber:
Das wird aber in dem Moment NOK, wo das für Hardwareflags missbraucht wird und bitkombinationen entstehen, die bestenfalls sinnlos sind. Durch den static_cast wird das aber möglich gemacht. Von daher sollte man Deine Formulierung "halbwegs typsicher" fett drucken und dreimal unterstreichen.
-
Wo ist das Problem? Durch static_cast<int> (oder was auch immer) wird aus dem Enum ein int!
Und als solches können solche Werte ruhig entstehen da sie für ints definiert sind.
-
Da wird aber per static_cast auf den enum zurückgecastet (s. Diverse Beiträge oben). Der cast enum -> int ist imizit bei der Veroderung
-
@Enumerator
Wo ist diesbezüglich der Unterschied ob man es jetzt mit enums oder gleich mit ints macht? Ints schützen dich auch nicht davor "verbotenet" Kombinationen zu bilden.Und wo ist der Unterschied zwischen normalen Flags und irgendwelchen Bits in irgendwelchen Registern?
Auch bei normalen APIs gibt es immer wieder Bitkombinationen die nicht erlaubt sind.Was man durch enums in diesem Fall trotzdem gewinnt, ist die Sicherheit nicht (unabsichtlich) Konstanten die für Register A definiert werden in Register B zu schreiben. Also dass wenn ich
TuWas(BLUBBCMD_BLUB, BLUBBMODE_USE_DMA)
schreibe, auch wirklich gemeint ist dass Blubb bitte mit DMA blubbern soll. Und ich nicht dummerweise die Parameter vertauscht habe,TuWas
die falschen Werte in die Register schreibt, und dadurch irgendwas komplett unerwartetes passiert.----
Oder andersrum: hast du was besseres anzubieten? Ich sag' ja nicht dass enums eine völlig optimale Lösung sind. Ich sag nur sie haben, wenn es um Flags geht, Vorteile gegenüber der Verwendung von "nackten" Integern. Und verwehre mich daher dagegen dass es "unsauber" wäre genau das zu machen.
ps:
Falls du das überlesen haben solltest...Der
static_cast
wird ja bei der von mir bevorzugten Variante nur innerhalb der | und & Operator-Funktionen für den Flags-Enum-Typ verwendet. D.h. an genau zwei Stellen pro Typ. Wenn man sich ein Makro dafür schreibt hat man es sogar nur an zwei Stellen im ganzen Programm, egal wie viele Flags-Typen man definiert.
Das reicht um den User gegen "sich unabsichtlich ins Knie schiessen" zu schützen.
Und gegen "sich absichtlich ins Knie schiessen" kann man in C++ sowieso nie etwas absichern.