Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

02.10.2019, 20:32

Best Practice für Bit-Flags in C++

Hallo Leute,

ich frage mich, wie man folgendes Problem "richtig löst" (bzw. wie ihr es lösen würdet):

Ich möchte irgendwie Bit-Flags in C++ verwenden mit folgenden Eigenschaften haben:
- Typensicher (keine implizite Konvertierung zu integer Typen)
- Es wird er globale namespace nicht mit häufigen symbolen "verschmutzt" (Bsp.: Bei Richtungen soll es zwar Direction::noth geben, aber north alleine trotzdem noch verwendbar)
- simple bitweise Operatoren (|=, &=, ^=, ~, !, &, |)
- am Ende soll es auf einem Mikroprozessor laufen (Arm-Cortex-M3), für den es aber eine fast vollständig geportete C++ Standard Library gibt.

Es gibt mehrere Möglichkeiten, von denen ich bis jetzt gehört habe (manche simpel, manche aufwendig, fast keine ideal):
- Einfach in verschiedenen Namespaces mit constexpr den Werten die Namen geben:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
namespace Direction {
 constexpr uint8_t north = 1 <<0;
 constexpr uint8_t east = 1 << 1;
}

int main()
{
 uint8_t WhereCanIGo = Direction::north | Direction::east;
}


- Möglichkeiten enum class mit einem underlying type verwenden:
* Alle nötigen Operatoren per Makro für jedes Enum erzeugen
* Per templates, type-traits und SFINAE die nötigen operatoren überladen (Nicht möglich, da für Enums in einer Klasse keine traits erzeugt werden können / Oder wie auch immer man das nennt)
* Eine Template Klasse, die alle Operatoren hat. Siehe Beispiel:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template<typename T = typename std::enable_if<std::is_enum<T>, T>>
class EnumAsFlag {
private:
 std::underlying_type<T>::type _val
public:
 // Alle Operatoren und Konstruktoren
};

namespace x {
 enum class Direction : uint8_t {
  north = 1 << 0,
  east = 1 << 1
 };
}

int main() {
 EnumAsFlag<x::Direction> WherCanIGo = x::Direction::north;
}


Das ist mein aktueller Favourite, weil dadurch auch klar wird, wann das enum nur eine Eigenschaft beinhält (x::Direction) und wann ich es als Flag als Kombination von mehreren Eigenschaften verwende (EnumAsFlag<x::Direction>)

- Ich hab auch irgendwas von bit fields / bit sets gelesen, hab mich aber noch nicht darüber schlau gemacht, und bin mir nicht sicher, ob das nicht zu viel Overhead wird, da alle anderen Methoden eigentlich vom Compiler zu einer sehr primitiven Variante optimiert werden können (glaub ich halt).

Also, wie würdet ihr das machen?

Mit freundlichen Grüßen,
Patrick Ziegler
Albert Einstein sagte: "2 Stunden mit einem netten Mädchen fühlen sich an wie 20 Minuten, 20 Minuten auf einem heißen Ofen fühlen sich an wie 2 Stunden. - Das ist Relativität"

2

03.10.2019, 00:56

https://en.cppreference.com/w/cpp/language/bit_field

Wenn du kein Bit Field nehmen kannst, weil du das z.B. auch in einer Datei speichern musst, würde ich einfach constexpr im namespace (oder struct) verwenden.

Du kannst aber auch gerne noch ein paar Stunden darüber nachdenken statt dich um die wichtigen Probleme/Features zu kümmern. ;)
Cube Universe
Entdecke fremde Welten auf deiner epischen Reise durchs Universum.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

3

06.10.2019, 14:11

Ich verwende dafür seit einiger Zeit so ein Makro:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define BITOPS_ENUM(T) \
    static_assert(static_cast<::std::underlying_type<T>::type>(T::None) == 0, "Enum " #T " must have \"None\" entry."); \
    FORCEINLINE static constexpr bool Has(T Value, T Requirement) noexcept { return (static_cast<::std::underlying_type<T>::type>(Value) & static_cast<::std::underlying_type<T>::type>(Requirement)) != 0; } \
    FORCEINLINE static constexpr T operator |(T First, T Second) noexcept { return static_cast<T>(static_cast<::std::underlying_type<T>::type>(First) | static_cast<::std::underlying_type<T>::type>(Second)); } \
    FORCEINLINE static constexpr T operator ^(T First, T Second) noexcept { return static_cast<T>(static_cast<::std::underlying_type<T>::type>(First) ^ static_cast<::std::underlying_type<T>::type>(Second)); } \
    FORCEINLINE static constexpr T operator &(T First, T Second) noexcept { return static_cast<T>(static_cast<::std::underlying_type<T>::type>(First) & static_cast<::std::underlying_type<T>::type>(Second)); } \
    FORCEINLINE static constexpr T operator ~(T Value) noexcept { return static_cast<T>(~static_cast<::std::underlying_type<T>::type>(Value)); } \
    FORCEINLINE static constexpr T operator <<(T Value, size_t Shift) noexcept { return static_cast<T>(static_cast<::std::underlying_type<T>::type>(Value) << Shift); } \
    FORCEINLINE static constexpr T operator >>(T Value, size_t Shift) noexcept { return static_cast<T>(static_cast<::std::underlying_type<T>::type>(Value) >> Shift); } \
    FORCEINLINE static CPP14_CONSTEXPR void operator |=(T& First, T Second) noexcept { First = static_cast<T>(static_cast<::std::underlying_type<T>::type>(First) | static_cast<::std::underlying_type<T>::type>(Second)); } \
    FORCEINLINE static CPP14_CONSTEXPR void operator ^=(T& First, T Second) noexcept { First = static_cast<T>(static_cast<::std::underlying_type<T>::type>(First) ^ static_cast<::std::underlying_type<T>::type>(Second)); } \
    FORCEINLINE static CPP14_CONSTEXPR void operator &=(T& First, T Second) noexcept { First = static_cast<T>(static_cast<::std::underlying_type<T>::type>(First) & static_cast<::std::underlying_type<T>::type>(Second)); } \
    FORCEINLINE static CPP14_CONSTEXPR void operator <<=(T& Value, size_t Shift) noexcept { Value = static_cast<T>(static_cast<::std::underlying_type<T>::type>(Value) << Shift); } \
    FORCEINLINE static CPP14_CONSTEXPR void operator >>=(T& Value, size_t Shift) noexcept { Value = static_cast<T>(static_cast<::std::underlying_type<T>::type>(Value) >> Shift); } \
    FORCEINLINE static size_t BitScanReverse(T Value) noexcept { return ::Diverse::BitScanReverse(static_cast<::std::underlying_type<T>::type>(Value)); } \
    FORCEINLINE static size_t BitScanForward(T Value) noexcept { return ::Diverse::BitScanForward(static_cast<::std::underlying_type<T>::type>(Value)); } \
    FORCEINLINE static size_t CountSetBits(T Value) noexcept { return ::Diverse::CountSetBits(static_cast<::std::underlying_type<T>::type>(Value)); }

Man setzt nach einer Enumeration ein BITOPS_ENUM(...) um die bitweise Operatoren darauf zu definieren.
Es gibt noch ne Hilffunktion Has, da man leider auf diese Weise nicht automatisch zu bool konvertieren kann und if ((MyEnumVar & ProjectNamespace::MyEnumClass::SettingA) != ProjectNamespace::MyEnumClass::None) mit der Zeit dann nervt.

Bei dieser Gelegenheit muss ich mir aber doch die Frage stellen, ob die Komplextität gerechtfertig ist. Vlt. sollte man doch einfach eine C-Enumerationen verwenden oder das analog deinem ersten Beispiel machen.

TGGC

1x Rätselkönig

Beiträge: 1 799

Beruf: Software Entwickler

  • Private Nachricht senden

Werbeanzeige