In einem C ++ - Projekt, an dem ich arbeite, habe ich einen Flag- Wert, der vier Werte haben kann. Diese vier Flags können kombiniert werden. Flags beschreiben die Datensätze in der Datenbank und können sein:
- Neuer Eintrag
- Datensatz gelöscht
- geänderter Datensatz
- vorhandener Datensatz
Nun möchte ich für jeden Datensatz dieses Attribut beibehalten, damit ich eine Aufzählung verwenden kann:
enum { xNew, xDeleted, xModified, xExisting }
An anderen Stellen im Code muss ich jedoch auswählen, welche Datensätze für den Benutzer sichtbar sein sollen, damit ich dies als einzelnen Parameter übergeben kann, z.
showRecords(xNew | xDeleted);
Es scheint also, dass ich drei mögliche Ansätze habe:
#define X_NEW 0x01
#define X_DELETED 0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08
oder
typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;
oder
namespace RecordType {
static const uint8 xNew = 1;
static const uint8 xDeleted = 2;
static const uint8 xModified = 4;
static const uint8 xExisting = 8;
}
Der Platzbedarf ist wichtig (Byte vs Int), aber nicht entscheidend. Mit Definitionen verliere ich die Typensicherheit und mit enum
etwas Speicherplatz (Ganzzahlen) und muss wahrscheinlich umwandeln, wenn ich eine bitweise Operation ausführen möchte. Mit const
I ich auch verlieren Art Sicherheit denken , da eine zufällige uint8
könnte irrtümlich erhalten.
Gibt es einen anderen saubereren Weg?
Wenn nicht, was würden Sie verwenden und warum?
PS Der Rest des Codes ist ziemlich sauber, modernes C ++ ohne #define
s, und ich habe Namespaces und Vorlagen in wenigen Räumen verwendet, daher kommen diese auch nicht in Frage.
quelle
enum RecordType : uint8_t
die Typensicherheit vonenum
mit der geringen Größe vonuint8_t
, obwohl Sie immer noch bitweise Operatoren bereitstellen müssen.Antworten:
Kombinieren Sie die Strategien, um die Nachteile eines einzelnen Ansatzes zu verringern. Ich arbeite in eingebetteten Systemen, daher basiert die folgende Lösung auf der Tatsache, dass ganzzahlige und bitweise Operatoren schnell, wenig Speicher und wenig Flash-Nutzung sind.
Platzieren Sie die Aufzählung in einem Namespace, um zu verhindern, dass die Konstanten den globalen Namespace verschmutzen.
Eine Aufzählung deklariert und definiert eine aktivierte Kompilierungszeit. Verwenden Sie immer die Typprüfung zur Kompilierungszeit, um sicherzustellen, dass Argumente und Variablen den richtigen Typ erhalten. Typedef ist in C ++ nicht erforderlich.
Erstellen Sie ein anderes Mitglied für einen ungültigen Status. Dies kann als Fehlercode nützlich sein. Zum Beispiel, wenn Sie den Status zurückgeben möchten, der E / A-Vorgang jedoch fehlschlägt. Es ist auch nützlich zum Debuggen; Verwenden Sie es in Initialisierungslisten und Destruktoren, um festzustellen, ob der Wert der Variablen verwendet werden soll.
Beachten Sie, dass Sie für diesen Typ zwei Zwecke haben. Verfolgen des aktuellen Status eines Datensatzes und Erstellen einer Maske zum Auswählen von Datensätzen in bestimmten Status. Erstellen Sie eine Inline-Funktion, um zu testen, ob der Wert des Typs für Ihren Zweck gültig ist. als Zustandsmarker gegen eine Zustandsmaske. Dies wird Fehler auffangen, da dies
typedef
nur einint
und ein Wert ist, wie er0xDEADBEEF
in Ihrer Variablen durch nicht initialisierte oder falsch ausgerichtete Variablen enthalten sein kann.Fügen Sie eine
using
Direktive hinzu, wenn Sie den Typ häufig verwenden möchten.Die Werteprüfungsfunktionen sind nützlich bei Asserts, um fehlerhafte Werte abzufangen, sobald sie verwendet werden. Je schneller Sie beim Laufen einen Fehler bemerken, desto weniger Schaden kann er anrichten.
Hier sind einige Beispiele, um alles zusammenzustellen.
Die einzige Möglichkeit, die Sicherheit korrekter Werte zu gewährleisten, besteht darin, eine spezielle Klasse mit Bedienerüberlastungen zu verwenden, die als Übung für einen anderen Leser verbleibt.
quelle
IsValidMask
keine (dh0
) ausgewählt werden kann?Vergiss die Definitionen
Sie werden Ihren Code verschmutzen.
Bitfelder?
Benutze das niemals . Es geht Ihnen mehr um Geschwindigkeit als um die Einsparung von 4 Zoll. Die Verwendung von Bitfeldern ist tatsächlich langsamer als der Zugriff auf einen anderen Typ.
Quelle: http://en.wikipedia.org/wiki/Bit_field :
Und wenn Sie mehr Gründe brauchen, um Bitfelder nicht zu verwenden, wird Raymond Chen Sie vielleicht in seinem Beitrag The Old New Thing Post überzeugen : Die Kosten-Nutzen-Analyse von Bitfeldern für eine Sammlung von Booleschen Werten unter http://blogs.msdn.com/oldnewthing/ archive / 2008/11/26 / 9143050.aspx
const int?
Es ist cool, sie in einen Namespace zu stellen. Wenn sie in Ihrer CPP- oder Header-Datei deklariert sind, werden ihre Werte eingefügt. Sie können diese Werte einschalten, aber die Kopplung wird dadurch leicht erhöht.
Ah ja: Entfernen Sie das statische Schlüsselwort . static ist in C ++ veraltet, wenn Sie wie Sie verwendet werden. Wenn uint8 ein Build-Typ ist, benötigen Sie dies nicht, um dies in einem Header zu deklarieren, der von mehreren Quellen desselben Moduls enthalten wird. Am Ende sollte der Code sein:
Das Problem dieses Ansatzes ist, dass Ihr Code den Wert Ihrer Konstanten kennt, was die Kopplung geringfügig erhöht.
Aufzählung
Das gleiche wie const int, mit einer etwas stärkeren Eingabe.
Sie verschmutzen jedoch immer noch den globalen Namespace. Übrigens ... Entfernen Sie das typedef . Sie arbeiten in C ++. Diese Typedefs von Aufzählungen und Strukturen verschmutzen den Code mehr als alles andere.
Das Ergebnis ist irgendwie:
Wie Sie sehen, verschmutzt Ihre Aufzählung den globalen Namespace. Wenn Sie diese Aufzählung in einen Namespace einfügen, haben Sie Folgendes:
extern const int?
Wenn Sie die Kopplung verringern möchten (dh die Werte der Konstanten ausblenden und sie so nach Bedarf ändern können, ohne dass eine vollständige Neukompilierung erforderlich ist), können Sie die Ints im Header als extern und in der CPP-Datei als konstant deklarieren , wie im folgenden Beispiel:
Und:
Sie können diese Konstanten jedoch nicht einschalten. Also, am Ende, wähle dein Gift ... :-p
quelle
Haben Sie std :: bitset ausgeschlossen? Sätze von Flags ist, wofür es ist. Machen
dann
Da es für Bitset eine Reihe von Operatorüberladungen gibt, können Sie dies jetzt tun
Oder etwas sehr Ähnliches - ich würde mich über Korrekturen freuen, da ich dies nicht getestet habe. Sie können die Bits auch nach Index referenzieren. Im Allgemeinen ist es jedoch am besten, nur einen Satz von Konstanten zu definieren, und RecordType-Konstanten sind wahrscheinlich nützlicher.
Angenommen, Sie haben Bitset ausgeschlossen, stimme ich für die Aufzählung .
Ich kaufe nicht, dass das Casting der Aufzählungen ein schwerwiegender Nachteil ist - OK, es ist also etwas laut, und das Zuweisen eines Wertes außerhalb des Bereichs zu einer Aufzählung ist ein undefiniertes Verhalten, sodass es theoretisch möglich ist, sich auf einem ungewöhnlichen C ++ in den Fuß zu schießen Implementierungen. Aber wenn Sie es nur tun, wenn es nötig ist (wenn Sie von int zu enum iirc wechseln), ist es ganz normaler Code, den die Leute zuvor gesehen haben.
Ich bin auch über die Platzkosten der Aufzählung zweifelhaft. uint8-Variablen und -Parameter verwenden wahrscheinlich nicht weniger Stapel als Ints, sodass nur die Speicherung in Klassen von Bedeutung ist. Es gibt einige Fälle, in denen das Packen mehrerer Bytes in eine Struktur gewinnt (in diesem Fall können Sie Aufzählungen in den uint8-Speicher und aus diesem heraus umwandeln), aber normalerweise wird das Auffüllen den Vorteil trotzdem zunichte machen.
Die Aufzählung hat also keine Nachteile im Vergleich zu den anderen und bietet Ihnen als Vorteil ein wenig Typensicherheit (Sie können keinen zufälligen ganzzahligen Wert zuweisen, ohne explizit zu wirken) und saubere Möglichkeiten, auf alles zu verweisen.
Für die Präferenz würde ich übrigens auch das "= 2" in die Aufzählung setzen. Es ist nicht notwendig, aber ein "Prinzip des geringsten Erstaunens" legt nahe, dass alle 4 Definitionen gleich aussehen sollten.
quelle
bitset
? Normalerweise bedeutet dieslong
für jedes Element einen (in meiner Implementierung iirc; ja, wie verschwenderisch) oder ähnlichen Integraltyp. Warum also nicht einfach unverhüllte Integrale verwenden? (oder heutzutage ohneconstexpr
Speicherplatz)bitset
die Gründe für die Klasse nie wirklich verstanden, abgesehen von dem , was in den umgebenden Diskussionen über 'ugh immer wieder als Unterströmung erscheint, müssen wir die unappetitlichen Wurzeln der Sprache auf niedriger Ebene vertuschen 'uint8
Variablen und Parameter werden wahrscheinlich nicht weniger Stapel verwenden alsints
" ist falsch. Wenn Sie eine CPU mit 8-Bit-Registern haben,int
benötigen Sie mindestens 2 Register, während Sieuint8_t
nur 1 benötigen. Daher benötigen Sie mehr Stapelspeicher, da Sie mit größerer Wahrscheinlichkeit keine Register mehr haben (was auch langsamer ist und die Codegröße erhöhen kann). abhängig vom Befehlssatz)). (Sie haben einen Typ, sollte esuint8_t
nicht seinuint8
)Hier sind einige Artikel zu const vs. macros vs. enums:
Aufzählungskonstanten für symbolische Konstanten im
Vergleich zu konstanten Objekten
Ich denke, Sie sollten Makros vermeiden, insbesondere da Sie den größten Teil Ihres neuen Codes in modernem C ++ geschrieben haben.
quelle
Verwenden Sie nach Möglichkeit KEINE Makros. Sie werden nicht allzu sehr bewundert, wenn es um modernes C ++ geht.
quelle
Aufzählungen wären angemessener, da sie "Bedeutung für die Bezeichner" sowie Typensicherheit bieten. Sie können deutlich erkennen, dass "xDeleted" vom Typ "RecordType" ist und "Typ eines Datensatzes" (wow!) Auch nach Jahren darstellt. Consts würden dafür Kommentare erfordern, außerdem müssten sie im Code auf und ab gehen.
quelle
Nicht unbedingt...
Nicht unbedingt - aber Sie müssen an den Speicherorten explizit sein ...
Sie können Operatoren erstellen, um die Schmerzen zu lindern:
Dasselbe kann bei jedem dieser Mechanismen passieren: Bereichs- und Wertprüfungen sind normalerweise orthogonal zur Typensicherheit (obwohl benutzerdefinierte Typen - dh Ihre eigenen Klassen - "Invarianten" über ihre Daten erzwingen können). Bei Aufzählungen kann der Compiler einen größeren Typ auswählen, um die Werte zu hosten, und eine nicht initialisierte, beschädigte oder einfach falsch gesetzte Aufzählungsvariable könnte ihr Bitmuster immer noch als eine Zahl interpretieren, die Sie nicht erwarten würden - im Vergleich zu einer anderen ungleich die Aufzählungskennungen, eine beliebige Kombination davon und 0.
Nun, am Ende funktioniert das bewährte bitweise ODER von Aufzählungen im C-Stil ziemlich gut, wenn Sie Bitfelder und benutzerdefinierte Operatoren im Bild haben. Sie können Ihre Robustheit mit einigen benutzerdefinierten Validierungsfunktionen und Zusicherungen wie in der Antwort von mat_geek weiter verbessern. Techniken, die häufig gleichermaßen für den Umgang mit Zeichenfolgen, Int, Doppelwerten usw. anwendbar sind.
Man könnte argumentieren, dass dies "sauberer" ist:
Ich bin gleichgültig: Die Datenbits packen enger, aber der Code wächst erheblich ... hängt davon ab, wie viele Objekte Sie haben, und die Lamdbas - so schön sie auch sind - sind immer noch chaotischer und schwerer zu finden als bitweise ORs.
Übrigens / - das Argument über die ziemlich schwache IMHO der Thread-Sicherheit - am besten als Hintergrundüberlegung in Erinnerung behalten, anstatt zu einer dominanten entscheidungsgetriebenen Kraft zu werden; Das Teilen eines Mutex über die Bitfelder hinweg ist eine wahrscheinlichere Vorgehensweise, auch wenn sie sich ihrer Packung nicht bewusst sind (Mutexe sind relativ umfangreiche Datenelemente - ich muss mir wirklich Sorgen um die Leistung machen, um in Betracht zu ziehen, mehrere Mutexe für Mitglieder eines Objekts zu haben, und ich würde genau hinschauen genug, um zu bemerken, dass es sich um Bitfelder handelte). Jeder Typ mit Unterwortgröße kann das gleiche Problem haben (z
uint8_t
. B. a ). Auf jeden Fall können Sie atomare Vergleichs- und Austauschvorgänge ausprobieren, wenn Sie eine höhere Parallelität wünschen.quelle
operator|
sollteunsigned int
vor der Anweisung in einen Integer-Typ ( ) umgewandelt werden|
.operator|
Andernfalls ruft sich der Wille rekursiv auf und verursacht einen Laufzeitstapelüberlauf. Ich schlage vor :return RecordType( unsigned(lhs) | unsigned(rhs) );
. CheersSelbst wenn Sie 4 Byte zum Speichern einer Aufzählung verwenden müssen (ich bin mit C ++ nicht so vertraut - ich weiß, dass Sie den zugrunde liegenden Typ in C # angeben können), lohnt es sich dennoch, Aufzählungen zu verwenden.
In der heutigen Zeit von Servern mit GB Arbeitsspeicher spielen Dinge wie 4 Byte gegenüber 1 Byte Speicher auf Anwendungsebene im Allgemeinen keine Rolle. Wenn in Ihrer speziellen Situation die Speichernutzung so wichtig ist (und Sie C ++ nicht dazu bringen können, ein Byte zum Sichern der Aufzählung zu verwenden), können Sie die Route 'static const' in Betracht ziehen.
Am Ende des Tages müssen Sie sich fragen, ob es sich lohnt, 'statische Konstante' für die 3 Byte Speichereinsparung für Ihre Datenstruktur zu verwenden.
Beachten Sie noch etwas anderes: IIRC: Auf x86 sind Datenstrukturen 4-Byte-ausgerichtet. Wenn Sie also keine Elemente mit Byte-Breite in Ihrer Datensatzstruktur haben, spielt dies möglicherweise keine Rolle. Testen Sie dies und stellen Sie sicher, dass dies der Fall ist, bevor Sie einen Kompromiss hinsichtlich der Wartbarkeit für Leistung / Platz eingehen.
quelle
int
wenn es nicht zu klein ist". [Wenn Sie den zugrunde liegenden Typ in C ++ 11 nicht angeben, wird das Legacy-Verhalten verwendet. Umgekehrt wird derenum class
zugrunde liegende Typ von C ++ 11 explizit standardmäßig verwendet,int
sofern nicht anders angegeben.]Wenn Sie die Typensicherheit von Klassen mit der Bequemlichkeit der Aufzählungssyntax und der Bitprüfung wünschen, sollten Sie Safe Labels in C ++ in Betracht ziehen . Ich habe mit dem Autor gearbeitet und er ist ziemlich schlau.
Aber Vorsicht. Am Ende verwendet dieses Paket Vorlagen und Makros!
quelle
Müssen Sie die Flag-Werte tatsächlich als konzeptionelles Ganzes weitergeben, oder haben Sie viel Code pro Flag? In jedem Fall könnte es klarer sein, dies als Klasse oder Struktur von 1-Bit-Bitfeldern zu haben:
Dann könnte Ihre Datensatzklasse eine Mitgliedsvariable struct RecordFlag haben, Funktionen können Argumente vom Typ struct RecordFlag annehmen usw. Der Compiler sollte die Bitfelder zusammenfügen, um Platz zu sparen.
quelle
Ich würde wahrscheinlich keine Aufzählung für solche Dinge verwenden, bei denen die Werte miteinander kombiniert werden können. Typischerweise schließen sich Aufzählungen gegenseitig aus.
Unabhängig von der verwendeten Methode verwenden Sie stattdessen diese Syntax für die tatsächlichen Werte, um klarer zu machen, dass es sich um Werte handelt, die Bits sind, die miteinander kombiniert werden können:
Wenn Sie eine Linksverschiebung verwenden, können Sie anzeigen, dass jeder Wert ein einzelnes Bit sein soll. Es ist weniger wahrscheinlich, dass später jemand etwas falsch macht, z. B. einen neuen Wert hinzufügt und ihm einen Wert von 9 zuweist.
quelle
Stellen Sie diese Fragen basierend auf KISS , hoher Kohäsion und geringer Kopplung -
Es gibt ein großartiges Buch " Large-Scale C ++ Software Design ", das Basistypen extern fördert, wenn Sie eine andere Abhängigkeit von Header-Datei / Schnittstelle vermeiden können, die Sie versuchen sollten.
quelle
Wenn Sie Qt verwenden, sollten Sie nach QFlags suchen . Die QFlags-Klasse bietet eine typsichere Möglichkeit zum Speichern von ODER-Kombinationen von Enum-Werten.
quelle
Ich würde lieber mit gehen
Einfach weil:
quelle
Nicht, dass ich gerne alles überentwickeln würde, aber manchmal kann es in diesen Fällen sinnvoll sein, eine (kleine) Klasse zu erstellen, um diese Informationen zu kapseln. Wenn Sie eine Klasse RecordType erstellen, hat diese möglicherweise folgende Funktionen:
void setDeleted ();
void clearDeleted ();
bool isDeleted ();
etc ... (oder was auch immer Konvention passt)
Es könnte Kombinationen validieren (falls nicht alle Kombinationen zulässig sind, z. B. wenn "neu" und "gelöscht" nicht beide gleichzeitig festgelegt werden können). Wenn Sie nur Bitmasken usw. verwendet haben, muss der Code, der den Status festlegt, validiert werden. Eine Klasse kann diese Logik auch kapseln.
Die Klasse bietet Ihnen möglicherweise auch die Möglichkeit, jedem Status aussagekräftige Protokollierungsinformationen hinzuzufügen. Sie können eine Funktion hinzufügen, um eine Zeichenfolgendarstellung des aktuellen Status usw. zurückzugeben (oder die Streaming-Operatoren '<<' verwenden).
Wenn Sie sich Sorgen um den Speicher machen, kann es sein, dass die Klasse nur ein 'char'-Datenelement hat. Nehmen Sie also nur eine kleine Menge Speicher (vorausgesetzt, es ist nicht virtuell). Abhängig von der Hardware usw. können natürlich Ausrichtungsprobleme auftreten.
Sie könnten die tatsächlichen Bitwerte für den Rest der 'Welt' nicht sichtbar haben, wenn sie sich in einem anonymen Namespace innerhalb der cpp-Datei und nicht in der Header-Datei befinden.
Wenn Sie feststellen, dass der Code, der die Aufzählung / # define / bitmask usw. verwendet, viel 'Support'-Code für ungültige Kombinationen, Protokollierung usw. enthält, ist die Kapselung in einer Klasse möglicherweise eine Überlegung wert. Natürlich sind einfache Probleme meistens mit einfachen Lösungen besser dran ...
quelle