Frage

In einem C++-Projekt, an dem ich arbeite, habe ich eine Flagge Art von 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, sodass ich eine Aufzählung verwenden könnte:

enum { xNew, xDeleted, xModified, xExisting }

Allerdings muss ich an anderen Stellen im Code auswählen, welche Datensätze für den Benutzer sichtbar sein sollen, daher möchte ich dies als einzelnen Parameter übergeben können, wie zum Beispiel:

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 Typsicherheit und mit enum Ich verliere etwas Platz (Ganzzahlen) und muss wahrscheinlich umwandeln, wenn ich eine bitweise Operation ausführen möchte.Mit const Ich glaube, ich verliere seit einem Zufall auch die Typsicherheit uint8 könnte versehentlich hineinkommen.

Gibt es einen anderen saubereren Weg?

Wenn nicht, was würden Sie verwenden und warum?

P.S.Der Rest des Codes ist ziemlich sauber, modernes C++ ohne #defines, und ich habe in einigen Bereichen Namespaces und Vorlagen verwendet, daher kommen auch diese nicht in Frage.

War es hilfreich?

Lösung

die Strategien vereinige die Nachteile eines einzigen Ansatz zu reduzieren. Ich arbeite in Embedded-Systemen, so dass die folgende Lösung basiert auf der Tatsache, dass integer und Bit-Operatoren sind schnell, mit wenig Speicher und niedrig in Flash-Nutzung.

Legen Sie die Enum in einem Namespace, die Konstanten zu verhindern, dass der globalen Namensraum zu verschmutzen.

namespace RecordType {

Eine Enumeration deklariert und definiert eine Kompilierung eingegeben checked. Verwenden Sie immer Zeit Typ kompilieren Überprüfung sicher, dass Argumente und Variablen zu machen sind, um die richtige Art gegeben. Es besteht keine Notwendigkeit für die typedef in C ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Erstellen Sie ein anderes Mitglied für einen ungültigen Zustand. Dies kann als Fehlercode nützlich sein; zum Beispiel, wenn Sie den Zustand, aber die I / O-Operation nicht zurückkehren wollen. Es ist auch nützlich für die Fehlersuche; verwenden Sie es in der Initialisierung Listen und Destruktoren zu wissen, ob der Wert sollte die Variable verwendet werden.

xInvalid = 16 };

Beachten Sie, dass Sie haben zwei Zwecke für diese Art. Um den aktuellen Status eines Datensatzes zu verfolgen und eine Maske erstellen, um Datensätze in bestimmten Staaten. Erstellen Sie eine Inline-Funktion zu testen, ob der Wert des Typs für Ihre Zwecke gültig ist; als Zustandsmarker vs einem Zustand Maske. Dies wird Bugs fangen, wie der typedef ist nur ein int und ein Wert wie 0xDEADBEEF durch uninitialised oder mispointed Variablen in variabel sein kann.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Fügen Sie eine using Richtlinie, wenn Sie wollen oft die Art verwenden.

using RecordType ::TRecordType ;

Der Wert Prüffunktionen nützlich sind, behauptet zu stoppen schlechte Werte, sobald sie verwendet werden. Je schneller Sie fangen einen Fehler beim Laufen, desto weniger Schaden kann es tun.

Hier sind einige Beispiele, sie alle zusammen zu setzen.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

Der einzige Weg, korrekten Wert um Sicherheit zu gewährleisten ist eine eigene Klasse mit Bedienungs Überlastungen zu verwenden, und das ist für einen anderen Leser als Übung.

Andere Tipps

Vergessen Sie die definiert

Sie werden Ihren Code verschmutzen.

bitfields?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Verwenden Sie nicht immer, dass . Sie sind mehr mit Geschwindigkeit als mit 4 Ints Ökonomisierung. Bit-Felder ist eigentlich langsamer als der Zugriff auf jede andere Art.

Jedoch Bit Mitglieder in structs haben praktische Nachteile. Erstens ändert sich die Reihenfolge der Bits im Speicher von Compiler-Compiler. Außerdem viele populären Compiler generieren ineffiziente Codes zum Lesen und Schreiben von Bit-Mitgliedern , und es gibt möglicherweise schweren Gewinde Sicherheitsfragen im Zusammenhang mit Bitfeldern (insbesondere auf Multi-Prozessor-Systemen) aufgrund die Tatsache, dass die meisten Maschinen nicht beliebige Mengen von Bits im Speicher manipulieren können, sondern muss stattdessen laden und ganze Worte speichern. z die folgenden nicht Thread-sicher, trotz der Verwendung eines Mutex
sein

Quelle: http://en.wikipedia.org/wiki/Bit_field :

Und wenn Sie brauchen mehr Gründe nicht Verwendung bitfields, vielleicht Raymond Chen finden Sie in seinem Die Post New Thing Old überzeugen: Die Kosten-Nutzen-Analyse Bitfelder für eine Sammlung von booleans http: // blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Setzen sie in einem Namensraum ist cool. Wenn sie in Ihrer CPP oder Header-Datei deklariert sind, werden ihre Werte inlined. Sie werden in der Lage sein, Schalter auf diesen Werten zu verwenden, aber es wird etwas Kopplung erhöhen.

Ach ja: entfernen Sie das Schlüsselwort static . statisch ist in C ++ als veraltet, wenn verwendet, wie Sie tun, und wenn uint8 ein buildin-Typ ist, werden Sie dies nicht brauchen dies in einem Header von mehreren Quellen des gleichen Moduls enthalten zu erklären. Am Ende sollte der Code sein:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Das Problem dieses Ansatzes ist, dass der Code den Wert Ihrer Konstanten kennt, die leicht die Kupplung erhöht.

Enum

Die gleiche wie const int, mit einer etwas stärkeren Typisierung.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Sie sind umweltfreundlich noch den globalen Namensraum, though. Durch die Art und Weise ... Entfernen Sie die typedef . Sie arbeiten in C ++. Diese typedefs von Aufzählungen und structs verschmutzen den Code mehr als alles andere.

Das Ergebnis ist ein bisschen:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Wie Sie sehen, Ihre ENUM ist die Verschmutzung der globalen Namespace. Wenn Sie diese Enumeration in einem Namensraum setzen, werden Sie so etwas wie haben:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

Wenn Sie möchten, Kopplung verringern (dh der Lage, die Werte der Konstanten zu verbergen, und so, ändern sie wie gewünscht, ohne eine vollständige erneute Kompilierung zu benötigen), können Sie die Ints als extern im Header deklarieren kann, und als Konstante in die CPP-Datei, wie im folgenden Beispiel:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

Und:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Sie werden nicht in der Lage sein, Schalter auf diese Konstanten zu verwenden, though. Also am Ende, wählen Sie Ihr Gift ... :-P

Haben Sie entschieden, std :: bitset aus? Sätze von Fahnen ist, was es für. Haben

typedef std::bitset<4> RecordType;

dann

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Da gibt es eine Reihe von Operator Überlastungen für bitset sind, können Sie jetzt tun

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

oder etwas sehr ähnlich zu dem - ich würde keine Korrekturen zu schätzen wissen, da ich nicht getestet haben. Sie können auch auf die Bits durch einen Index beziehen, aber es ist im Allgemeinen am besten nur ein Satz von Konstanten zu definieren, und Record Konstanten sind wahrscheinlich mehr nützlich.

Angenommen, Sie bitset ausgeschlossen haben, ich stimme für die Enum .

ich nicht kaufen, dass die Aufzählungen Gießen ein schwerwiegender Nachteil ist - OK, so ist es ein bisschen laut, und Zuweisen einer Out-of-Range-Wert zu einem Enum ist nicht definiertes Verhalten, so dass es theoretisch möglich ist, selbst in den Fuß zu schießen auf einige ungewöhnliche C ++ Implementierungen. Aber wenn man es nur bei Bedarf (das ist, wenn sie von int gehen iirc zu ENUM), ist es vollkommen normal Code, dass die Menschen gesehen haben.

Ich bin skeptisch, was jeden Raum Kosten des ENUM auch. uint8 Variablen und Parameter werden wahrscheinlich nicht weniger als Stapel Ints verwenden, so dass nur die Lagerung in Klassen Angelegenheiten. Es gibt einige Fälle, in denen mehrere Bytes in einer Struktur Verpackung gewinnen (in diesem Fall können Sie Aufzählungen in und aus uint8 Lagerung cast), aber in der Regel Polsterung wird den Nutzen ohnehin töten.

So ist die Enum keine Nachteile hat im Vergleich zu den anderen, und als ein Vorteil gibt Ihnen ein wenig Typsicherheit (Sie können nicht einige zufällige Integer-Wert zuweisen, ohne explizit zu Gießen) und saubere Wege, auf alles bezieht.

Für Präferenz würde ich setze auch die „= 2“ in der Enum, nebenbei bemerkt. Es ist nicht notwendig, sondern ein „Prinzip der geringsten Erstaunen“ deutet darauf hin, dass alle vier Definitionen gleich aussehen sollte.

Hier sind einige Artikel zu konst vs. Makros vs. Aufzählungen:

Symbolische Konstanten
Aufzählungskonstanten vs. Constant Objekte

Ich glaube, Sie Makros vor allem vermeiden sollten, da Sie die meisten Ihrer neuen Code geschrieben in modernen C ++.

Wenn möglich, verwenden Sie keine Makros. Sie sind nicht zu viel bewundert, wenn es um moderne C ++ kommt.

Aufzählungen wäre besser geeignet, da sie bieten sowie Typsicherheit „zu den Kennungen Bedeutung“. Man kann sagen, „xDeleted“ eindeutig von „Record“ und dass „Art eines Datensatzes“ (wow!) Auch nach Jahren repräsentieren. Consts würde Kommentare verlangen, dass, auch würden sie gehen nach oben und unten in Code.

Mit Definitionen verliere ich die Typsicherheit

Nicht unbedingt...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

und mit enum verliere ich etwas Platz (Ganzzahlen)

Nicht unbedingt – aber Sie müssen die Speicherorte explizit angeben ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

und muss wahrscheinlich umwandeln, wenn ich eine bitweise Operation durchführen möchte.

Sie können Operatoren erstellen, um dies zu vereinfachen:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Mit const verliere ich glaube ich auch die Typsicherheit, da versehentlich ein zufälliges uint8 eindringen könnte.

Das Gleiche kann bei jedem dieser Mechanismen passieren:Bereichs- und Wertprüfungen sind normalerweise orthogonal zur Typsicherheit (obwohl benutzerdefinierte Typen – d. h.Ihre eigenen Klassen - können „Invarianten“ über ihre Daten erzwingen).Bei Aufzählungen steht es dem Compiler frei, einen größeren Typ zum Hosten der Werte auszuwählen, 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 keiner anderen Zahl die Aufzählungsbezeichner, eine beliebige Kombination davon und 0.

Gibt es einen anderen saubereren Weg?/ Wenn nicht, was würden Sie verwenden und warum?

Nun, am Ende funktioniert das bewährte bitweise OR von Aufzählungen im C-Stil ziemlich gut, sobald Sie Bitfelder und benutzerdefinierte Operatoren im Bild haben.Sie können Ihre Robustheit mit einigen benutzerdefinierten Validierungsfunktionen und Behauptungen weiter verbessern, wie in der Antwort von mat_geek;Techniken, die oft gleichermaßen auf den Umgang mit String-, Int-, Double-Werten usw. anwendbar sind.

Man könnte argumentieren, dass dies „sauberer“ ist:

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

Mir ist gleichgültig:Die Datenbits werden dichter gepackt, 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 schwieriger richtig zu machen als bitweise ORs.

Übrigens / – das Argument, dass die Thread-Sicherheit meiner Meinung nach ziemlich schwach ist – sollte man sich am besten als Hintergrundüberlegung merken, anstatt zu einer dominanten Entscheidungskraft zu werden;Die gemeinsame Nutzung eines Mutex über die Bitfelder hinweg ist eine wahrscheinlichere Praxis, auch wenn man sich ihrer Packung nicht bewusst ist (Mutexe sind relativ umfangreiche Datenelemente – ich muss mir wirklich Sorgen um die Leistung machen, wenn ich in Betracht ziehe, 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 könnte das gleiche Problem haben (z. B.A uint8_t).Wie auch immer, Sie könnten atomare Vergleichs- und Austauschoperationen ausprobieren, wenn Sie unbedingt eine höhere Parallelität wünschen.

Auch wenn Sie 4 Byte zu verwenden, haben eine ENUM zu speichern (Ich bin nicht so vertraut mit C ++ - Ich weiß, dass Sie den zugrunde liegenden Typen in C # angeben können), dann ist es immer noch wert -. Aufzählungen verwenden

In der heutigen Zeit von Servern mit GBs des Gedächtnisses, Dinge wie 4 Bytes gegenüber 1 Byte Speicher auf der Anwendungsebene in der Regel keine Rolle. Natürlich, wenn in Ihrer speziellen Situation, die Speichernutzung ist so wichtig (und man kann nicht C ++ ein Byte verwenden, um die ENUM nach hinten), dann können Sie die ‚static const‘ Route prüfen.

Am Ende des Tages muss man sich fragen, lohnt es sich, die Wartung Hit ‚static const‘ für die 3 Byte Speicher Einsparungen für Ihre Datenstruktur?

Etwas anderes im Auge zu behalten - IIRC, auf x86, Datenstrukturen sind 4-Byte ausgerichtet ist, so dass, wenn Sie eine Reihe von Byte-Breite Elementen in Ihrer ‚Record‘ Struktur haben, kann es nicht wirklich wichtig ist. Test und stellen Sie sicher, es tut, bevor Sie einen Kompromiss in Wartbarkeit für die Leistung / Raum machen.

Wenn Sie die Typsicherheit von Klassen wollen, mit dem Komfort der Aufzählung Syntax und wenig Kontrolle, betrachtet Sicher Labels in C ++ . Ich habe mit dem Autor gearbeitet, und er ist ziemlich smart.

Beachten Sie aber. Am Ende werden in diesen Paketvorlagen und Makros!

Sie benötigen tatsächlich um die Flag-Werte als konzeptionelles Ganze passieren, oder wollen Sie eine Menge pro-Flag Code haben? So oder so, ich denke, das als Klasse mit oder Struktur von 1-Bit-bitfields könnte tatsächlich klarer:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Dann könnte Ihre Datensatzklasse eine Struktur RecordFlag Membervariable haben, Funktionen Argumente vom Typ struct RecordFlag nehmen, etc. Der Compiler sollte die bitfields packen zusammen, platzsparend.

Ich würde wahrscheinlich nicht eine Enumeration für diese Art einer Sache verwenden, wo die Werte miteinander kombiniert werden können, typische Aufzählungen sind sie gegenseitig ausschließende Zustände.

Aber je nachdem, welche Methode Sie verwenden, um es noch klar, dass diese Werte sind, die Bits sind, die miteinander kombiniert werden können, verwenden Sie die folgende Syntax für die Istwerte statt:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

eine Linksverschiebung dort Verwendung hilft, um anzuzeigen, dass jeder Wert soll ein einziges Bit sein, ist es weniger wahrscheinlich, dass später jemand etwas falsch machen würde, wie ein neuen Wert hinzufügen, und weisen Sie ihm etwas einen Wert von 9

Basierend auf KISS rel="nofollow hohe Kohäsion und geringe Kopplung , stellen diese Fragen -

  • Wer wissen muss? meine Klasse, meine Bibliothek, andere Klassen, andere Bibliotheken, 3. Parteien
  • Welche Abstraktionsebene muss ich bieten? Hat der Verbraucher-Bit-Operationen verstehen.
  • Will ich muss von VB / C # Schnittstelle etc?

Es ist ein großes Buch " Groß C ++ Software Design " dies fördert außen Basistypen, wenn Sie einen anderen Header-Datei / Schnittstelle dependancy vermeiden können Sie versuchen sollten.

Wenn Sie Qt verwenden, sollten Sie sich auch für QFlags . Die QFlags-Klasse stellt eine typsichere Art und Weise von OR-Kombinationen von ENUM-Werten gespeichert werden.

Ich würde eher gehen mit

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Ganz einfach, weil:

  1. Es ist sauberer und es macht den Code lesbar und wartbar.
  2. Es logisch gruppiert die Konstanten.
  3. Programmierers Zeit ist noch wichtiger ist, es sei denn Ihr Job ist diese 3 Bytes zu speichern.

Nicht, dass Ich mag alles über-Ingenieur, aber manchmal in diesen Fällen kann es sich lohnen, eine (kleine) Klasse erstellen, diese Informationen zu kapseln. Wenn Sie eine Klasse Record erstellen dann könnte es hat Funktionen wie:

void setDeleted ();

void clearDeleted ();

bool isDeleted ();

etc ... (oder was auch immer Konvention Anzüge)

Es könnte Kombinationen bestätigen (in dem Fall, dass nicht alle Kombinationen der geltende Rechtslage, zB wenn ‚neue‘ und ‚gestrichen‘ können nicht beide gleichzeitig eingestellt werden). Wenn Sie nur Bitmasken verwendet etc dann der Code, den Zustand überprüfen muss einstellt, kann eine Klasse auch diese Logik kapseln.

Die Klasse kann Ihnen auch die Möglichkeit gibt sinnvolle Protokollierung Informationen zu jedem Zustand zu befestigen, könnten Sie eine Funktion hinzufügen, eine String-Darstellung des aktuellen Status zurückzukehren usw. (oder die Streaming-Operatoren ‚<<‘).

Für alle, dass, wenn Sie sich Sorgen um Speicher sind, könnten Sie immer noch die Klasse haben nur haben einen ‚char‘ Datenelement, so dass nur eine geringe Menge an Speicher nehmen (vorausgesetzt, es ist nicht virtuell). Natürlich abhängig von der Hardware etc Fragen Ausrichtung Sie haben können.

Sie könnten die tatsächlichen Bit-Werte nicht sichtbar für den Rest der ‚Welt‘, wenn sie in einem anonymen Namespace in der CPP-Datei sind nicht in der Header-Datei.

Wenn Sie feststellen, dass der Code mit dem Enum / # definieren / bitmask etc hat eine Menge ‚Unterstützung‘ Code mit ungültigen Kombinationen zu beschäftigen, Logging etc. dann Verkapselung in einer Klasse kann eine Überlegung wert sein. Natürlich einfache Probleme die meiste Zeit mit einfachen Lösungen sind besser dran ...

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