Bagaimana cara menggunakan enums sebagai flag di C ++?


187

Mengobati enum s sebagai flag berfungsi dengan baik di C # melalui [Flags]atribut, tetapi apa cara terbaik untuk melakukan ini di C ++?

Sebagai contoh, saya ingin menulis:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Namun, saya mendapatkan kesalahan kompiler tentang int/enum konversi. Apakah ada cara yang lebih baik untuk mengekspresikan hal ini daripada sekadar pengecoran tumpul? Lebih disukai, saya tidak ingin bergantung pada konstruksi dari perpustakaan pihak ke-3 seperti boost atau Qt.

EDIT: Seperti yang ditunjukkan dalam jawaban, saya dapat menghindari kesalahan kompiler dengan menyatakan seahawk.flagssebagai int. Namun, saya ingin memiliki mekanisme untuk menegakkan keamanan tipe, jadi seseorang tidak dapat menulis seahawk.flags = HasMaximizeButton.


Sejauh yang saya tahu dalam Visual C ++ 2013 [Flags]atribut berfungsi dengan baik yaitu:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, Tidak, itu tidak bekerja dengan C ++ (2015 juga). Apakah maksud Anda C #?
Ajay

5
@rivanov, Atribut [Flags] hanya berfungsi dengan .Net Framework di C ++ CLI, C ++ asli tidak mendukung atribut tersebut.
Zoltan Tirinda

Jawaban:


251

Cara "benar" adalah mendefinisikan operator bit untuk enum, sebagai:

enum AnimalFlags
{
    HasClaws   = 1,
    CanFly     = 2,
    EatsFish   = 4,
    Endangered = 8
};

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

Dll sisa operator bit. Ubah sesuai kebutuhan jika rentang enum melebihi kisaran int.


42
^ ini. Satu-satunya pertanyaan adalah bagaimana mengotomatisasi / templatize definisi operator sehingga Anda tidak harus terus-menerus mendefinisikannya setiap kali Anda menambahkan enum baru.
eodabash

10
Juga, apakah para pemeran dari int arbitrer kembali ke tipe enum valid, bahkan jika nilai int tidak sesuai dengan salah satu pengidentifikasi enum?
Ingo Schalk-Schupp

8
Ini omong kosong. Anggota mana AnimalFlagsyang diwakili oleh ungkapan HasClaws | CanFly? Ini bukan untuk apa enum. Gunakan bilangan bulat dan konstanta.
Lightness Races dalam Orbit

26
@LightnessRacesinOrbit: Itu tidak benar. Domain dari tipe enum adalah domain dari tipe yang mendasarinya - hanya yang tertentu saja yang diberi nama. Dan untuk menjawab pertanyaan Anda: Anggota " (HasClaws | CanFly)".
Xeo

5
@MarcusJ: membatasi nilai Anda menjadi pangkat 2 memungkinkan Anda untuk menggunakan enum Anda sebagai bit-flags. Jadi jika Anda mendapatkan angka 3, Anda mengetahuinya HasClaws(= 1) dan CanFly(= 2). Jika sebaliknya Anda hanya menetapkan nilai 1 hingga 4 langsung dan Anda mendapatkan 3, itu mungkin tunggal EatsFish, atau kombinasi lagi dari HasClawsdan CanFly. Jika enumerasi Anda menunjukkan status eksklusif hanya maka nilai berturut-turut baik-baik saja, tetapi kombinasi flag membutuhkan nilai tersebut menjadi bit-eksklusif.
Christian Severin

122

Catatan (juga topik yang agak aneh): Cara lain untuk membuat bendera unik dapat dilakukan menggunakan sedikit pergeseran. Saya sendiri lebih mudah membaca ini.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3, // binary 1000
};

Ini dapat menyimpan nilai hingga int sehingga, sebagian besar waktu, 32 bendera yang jelas tercermin dalam jumlah shift.


2
Bisakah Anda menghapus koma terakhir (3,) dan menambahkan titik dua setelah} untuk membuat kode mudah disalin dan tempel? Terima kasih
Katu

4
Tidak disebutkan heksidimal? Penghujatan!
Pharap

1
@Jamie, kardinal selalu memulai dengan 1, hanya ordinals yang bisa mulai dengan 0 atau 1, tergantung pada siapa Anda berbicara.
Michael

2
@Michael, itu benar! Dalam enum, Anda biasanya memesan 0 untuk BLAH_NONE. :-) Terima kasih telah menggetarkan memori itu!
Jamie

1
@ Katu • koma berlebihan pada enumerasi akhir diizinkan oleh standar. Saya tidak suka, tapi saya sudah tahu apa yang akan dikatakan Stroustrup kepada saya ... "Anda tidak suka? Yah, jangan ragu untuk membuat bahasa Anda sendiri. Saya menyukainya."
Eljay

55

Untuk orang-orang malas seperti saya, inilah solusi tempelated untuk menyalin & menempel:

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }

23
+1 Kemalasan adalah salah satu dari tiga kebajikan utama seorang programmer: threevirtues.com
Pharap

10
Ini adalah solusi yang sangat bagus, hanya berhati-hati bahwa itu akan dengan senang hati memberikan operasi bitwise untuk semua tipe. Saya menggunakan sesuatu yang serupa, tetapi dengan penambahan ciri-ciri yang mengidentifikasi tipe yang ingin saya terapkan, dikombinasikan dengan sedikit sihir enable_if.
Rai

@Rai: Anda selalu dapat meletakkannya di namespace dan usingjika perlu, sama seperti rel_ops.
Yakov Galka

1
@ybungalobill, tetapi Anda masih akan memiliki masalah yang sama dengan operasi yang diterapkan pada semua jenis dalam lingkup penggunaan, yang mungkin akan cocok dengan enum? Saya pikir sifat-sifat yang paling mungkin diperlukan.
Rai

19
Jangan gunakan kode ini. Ini membuka pintu bagi kelas APAPUN untuk dioperasikan oleh kesalahan. Kode juga menggunakan cor gaya lama yang tidak akan melewati kompilasi ketat GCC shitalshah.com/p/… .
Shital Shah

44

Catatan jika Anda bekerja di lingkungan Windows, ada DEFINE_ENUM_FLAG_OPERATORSmakro yang didefinisikan di winnt.h yang melakukan pekerjaan untuk Anda. Jadi dalam hal ini, Anda dapat melakukan ini:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)

seahawk.flags = CanFly | EatsFish | Endangered;

44

Apa jenis variabel seahawk.flags?

Dalam C ++ standar, enumerasi tidak aman untuk tipe. Mereka adalah bilangan bulat yang efektif.

AnimalFlags TIDAK boleh menjadi tipe variabel Anda. Variabel Anda harus int dan kesalahannya akan hilang.

Menempatkan nilai heksadesimal seperti yang disarankan orang lain tidak diperlukan. Tidak ada bedanya.

Nilai enum adalah tipe int secara default. Jadi, Anda pasti bisa bitwise ATAU menggabungkannya dan menggabungkannya dan menyimpan hasilnya dalam sebuah int.

Tipe enum adalah subset terbatas int yang nilainya merupakan salah satu dari nilai yang disebutkan. Karenanya, saat Anda membuat beberapa nilai baru di luar rentang itu, Anda tidak bisa menetapkannya tanpa melakukan casting ke variabel tipe enum Anda.

Anda juga dapat mengubah tipe nilai enum jika diinginkan, tetapi tidak ada gunanya untuk pertanyaan ini.

EDIT: Poster mengatakan mereka peduli dengan keamanan jenis dan mereka tidak menginginkan nilai yang seharusnya tidak ada di dalam tipe int.

Tapi itu akan menjadi tipe yang tidak aman untuk menempatkan nilai di luar rentang AnimalFlags di dalam variabel tipe AnimalFlags.

Ada cara aman untuk memeriksa di luar nilai rentang meskipun di dalam tipe int ...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

enum AnimalFlags {
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8,

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Di atas tidak menghentikan Anda dari menempatkan bendera tidak valid dari enum berbeda yang memiliki nilai 1,2,4, atau 8.

Jika Anda menginginkan keamanan tipe absolut, Anda cukup membuat std :: set dan menyimpan setiap flag di dalamnya. Ini bukan ruang efisien, tetapi tipe aman dan memberi Anda kemampuan yang sama seperti int bitflag.

C ++ 0x note: Enums yang diketik dengan kuat

Dalam C ++ 0x akhirnya Anda bisa mengetikkan nilai safe enum ....

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error

4
Nilai enum bukan bilangan bulat, tetapi sangat mudah dikonversi menjadi bilangan bulat. Jenisnya HasClaws | CanFlyadalah beberapa tipe integer, tetapi tipenya HasClawsadalah AnimalFlags, bukan tipe integer.
Karu

1
Ah, tapi bagaimana jika kita mendefinisikan rentang enum yang benar bukan hanya nilai flag individu tetapi juga kombinasi bitwise mereka. Kemudian jawaban eidolon benar, dan menyatakan bahwa hanya kombinasi enum flag yang benar yang dapat dilewati sebagai tipe itu.
Scott

3
@ Esc: Perlu dicatat bahwa standar C ++ mendefinisikan kisaran nilai valid dari instance enum dengan cara itu. "untuk enumerasi di mana emin adalah enumerator terkecil dan emax adalah yang terbesar, nilai-nilai enumerasi adalah nilai-nilai dalam rentang bmin ke bmax, didefinisikan sebagai berikut: Biarkan K menjadi 1 untuk representasi komplemen dua dan 0 untuk satu ' representasi komplemen atau sign-magnitude. bmax adalah nilai terkecil yang lebih besar dari atau sama dengan max(|emin| − K, |emax|)dan sama dengan (1u<<M) - 1, di mana Mbilangan bulat non-negatif. "
Ben Voigt

Bagi mereka yang (seperti saya) hanya menginginkan sesuatu yang praktis yang memungkinkan nilai-nilai enum dimanipulasi bitwise dan tidak terlihat terlalu jelek dengan templat dan tipe casting, ini adalah solusi yang bagus; cukup tentukan variabel yang akan diketik int.
Eric Sokolowsky

Perhatikan juga bahwa dalam C ++, regular enumtidak secara teknis default intsebagai tipe yang mendasarinya (baik pre-C ++ 11 (IIRC), atau post-C ++ 11 ketika tidak ada tipe dasar yang ditentukan), meskipun enum class demikian . Sebaliknya, tipe yang mendasarinya default ke sesuatu yang cukup besar untuk mewakili semua enumerator, dengan satu-satunya aturan keras yang nyata bahwa itu hanya lebih besar daripada intjika secara eksplisit perlu . Pada dasarnya, tipe yang mendasarinya ditentukan sebagai (diparafrasekan) "apa pun yang bekerja, tapi itu mungkin int kecuali enumerator terlalu besar untuk int".
Justin Time - Pasang kembali Monica

26

Saya menemukan jawaban yang saat ini diterima oleh eidolon terlalu berbahaya. Pengoptimal kompiler mungkin membuat asumsi tentang nilai yang mungkin di enum dan Anda mungkin mendapatkan sampah kembali dengan nilai yang tidak valid. Dan biasanya tidak ada yang ingin mendefinisikan semua permutasi yang mungkin dalam flag enum.

Seperti yang dinyatakan oleh Brian R. Bondy di bawah ini, jika Anda menggunakan C ++ 11 (yang semua orang seharusnya, itu bagus) Anda sekarang dapat melakukan ini dengan lebih mudah dengan enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Ini memastikan ukuran dan kisaran nilai yang stabil dengan menentukan jenis enum, menghambat downcasting enum otomatis ke ints dll. Dengan menggunakan enum class, dan menggunakanconstexpr untuk memastikan kode untuk operator mendapatkan inline dan dengan demikian secepat nomor biasa.

Untuk orang-orang yang terjebak dengan dialek pra-11 C ++

Jika saya terjebak dengan kompiler yang tidak mendukung C ++ 11, saya akan menggunakan pembungkus tipe int di kelas yang kemudian hanya mengizinkan penggunaan operator bitwise dan tipe dari enum itu untuk menetapkan nilainya:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Anda dapat mendefinisikan ini cukup seperti enum + typedef biasa:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

Dan penggunaannya juga mirip:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

Dan Anda juga bisa mengesampingkan tipe dasar untuk enum biner-stabil (seperti C ++ 11's enum foo : type) menggunakan parameter templat kedua, yaitu typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Saya menandai operator booloverride dengan explicitkata kunci C ++ 11 untuk mencegahnya menghasilkan konversi int, karena hal itu dapat menyebabkan set flag pada akhirnya diciutkan menjadi 0 atau 1 saat menuliskannya. Jika Anda tidak dapat menggunakan C ++ 11, biarkan kelebihan itu dan tulis ulang syarat pertama dalam contoh penggunaan sebagai (myFlags & EFlagTwo) == EFlagTwo.


Sebagai catatan, saya akan merekomendasikan bahwa contoh operator didefinisikan pada awal penggunaan std::underlying_typealih-alih mengkode jenis tertentu, atau bahwa tipe yang mendasarinya disediakan dan digunakan sebagai jenis alias alih-alih secara langsung. Dengan begitu, perubahan pada tipe yang mendasarinya akan menyebar secara otomatis, alih-alih harus dilakukan secara manual.
Justin Time - Reinstate Monica

17

Cara termudah untuk melakukan ini seperti yang ditunjukkan di sini , menggunakan bitet kelas perpustakaan standar .

Untuk meniru fitur C # dengan cara yang aman, Anda harus menulis pembungkus template di sekitar bitset, mengganti argumen int dengan enum yang diberikan sebagai parameter tipe ke templat. Sesuatu seperti:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;

4
Lihat ini untuk kode yang lebih lengkap: codereview.stackexchange.com/questions/96146/…
Shital Shah

11

Menurut pendapat saya sejauh ini tidak ada jawaban yang ideal. Menjadi ideal saya harapkan solusinya:

  1. Mendukung ==, !=, =, &, &=, |, |=dan ~operator dalam arti konvensional (yaitu a & b)
  2. Jadilah tipe aman, yaitu tidak mengizinkan nilai-nilai yang tidak disebutkan seperti literal atau tipe integer ditugaskan (kecuali untuk kombinasi bitwise dari nilai-nilai yang disebutkan) atau mengizinkan variabel enum untuk ditugaskan ke tipe integer
  3. Izin ekspresi seperti if (a & b)...
  4. Tidak memerlukan makro jahat, fitur implementasi spesifik, atau peretasan lainnya

Sebagian besar solusi sejauh ini jatuh pada poin 2 atau 3. WebDancer adalah penutup dalam pendapat saya tetapi gagal pada poin 3 dan perlu diulang untuk setiap enum.

Solusi yang saya usulkan adalah versi umum dari WebDancer yang juga membahas poin 3:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

enum class AnimalFlags : uint8_t 
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Ini menciptakan kelebihan dari operator yang diperlukan tetapi menggunakan SFINAE untuk membatasi mereka untuk jenis yang disebutkan. Perhatikan bahwa untuk kepentingan singkatnya saya belum mendefinisikan semua operator tetapi satu-satunya yang berbeda adalah &. Operator saat ini bersifat global (yaitu berlaku untuk semua jenis yang disebutkan) tetapi ini dapat dikurangi baik dengan menempatkan kelebihan beban dalam ruang nama (apa yang saya lakukan), atau dengan menambahkan kondisi SFINAE tambahan (mungkin menggunakan tipe dasar tertentu, atau alias tipe khusus yang dibuat khusus ). Ini underlying_type_tadalah fitur C ++ 14 tetapi tampaknya didukung dengan baik dan mudah ditiru untuk C ++ 11 dengan sederhanatemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Meskipun solusi yang Anda usulkan bekerja sangat baik, itu juga memperkenalkan pola ini untuk enum yang tidak dimaksudkan untuk diperlakukan sebagai bendera. Mungkin itulah alasan penggunaan makro (jahat) seperti DEFINE_ENUM_FLAG_OPERATORS dari Microsoft.
WebDancer

@ WebDancer, Anda tentu saja benar, tetapi kemudian saya sudah mengatakan itu dalam jawaban saya. Saya juga menyarankan dua cara untuk mengatasi masalah ini - memasukkannya ke namespace atau menggunakan kondisi SFINAE yang lebih ketat.
Trevor

Maksud saya adalah kecuali Anda membuat namespace yang benar - benar sempit (mis. Namespace AllMyFlagEnums) atau memiliki kondisi SFINAE yang dalam beberapa cara memilih hanya beberapa enum yang tepat, kode ini dipatahkan dalam pikiran saya. Daripada mempertaruhkan ini, saya menyalin & menempel "templat teks" di mana saya hanya mengganti nama enum, dan terkadang makro "jahat". Saya berharap ada cara yang lebih baik.
WebDancer

Pertama, itu hanya akan menimbulkan masalah jika di tempat lain dalam kode Anda Anda perlu melakukan salah satu hal yang dimaksudkan untuk berhenti misalnya menetapkan literal, integer atau elemen dari enum lain. Kalau tidak, enum yang dimodifikasi berperilaku seperti enum biasa misalnya elemen tidak perlu harus kekuatan dua dan tugas, perbandingan dan operasi bitwise bekerja seperti biasa. Jika Anda benar-benar harus menetapkan literal atau campuran enum, Anda masih dapat secara eksplisit melemparkan, dengan keuntungan tambahan bahwa niat Anda akan lebih jelas. Jadi kemungkinan tidak akan ada kebutuhan untuk mengurangi ruang lingkup.
Trevor

Kedua, jika bahkan jika Anda perlu mengurangi ruang lingkup, namespace mungkin tidak perlu sempit - meskipun itu akan tergantung pada apa yang Anda lakukan. Jika Anda bekerja di perpustakaan maka mungkin Anda sudah memiliki kode yang bergantung pada enum di namespace, maka kode enum hanya berjalan di namespace yang sama. Jika Anda memerlukan perilaku enum untuk suatu kelas (mungkin Anda ingin menggunakan enum sebagai argumen metode atau variabel anggota kelas) maka masukkan kode enum di kelas untuk efek yang sama. Intinya adalah Anda tidak perlu membungkus namespace hanya sekitar enum - meskipun Anda bisa.
Trevor

8

Standar C ++ secara eksplisit membicarakan hal ini, lihat bagian "17.5.2.1.3 Jenis Bitmask":

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Dengan "templat" ini, Anda dapat:

enum AnimalFlags : unsigned int
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

Dan serupa untuk operator lainnya. Perhatikan juga "constexpr", diperlukan jika Anda ingin kompiler dapat menjalankan waktu kompilasi operator.

Jika Anda menggunakan C ++ / CLI dan ingin dapat menetapkan untuk enum anggota kelas ref Anda perlu menggunakan referensi pelacakan sebagai gantinya:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

CATATAN: Sampel ini tidak lengkap, lihat bagian "17.5.2.1.3 Jenis bitmask" untuk satu set lengkap operator.


6

Saya menemukan diri saya mengajukan pertanyaan yang sama dan menghasilkan solusi berbasis C ++ 11 yang umum, mirip dengan soru:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

Tampilannya bisa ditingkatkan sesuai selera. Maka dapat digunakan seperti:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

2
Lihat ini untuk kode yang lebih baik dan lebih lengkap: codereview.stackexchange.com/questions/96146/…
Shital Shah

5
Kecuali untuk saya menggunakan numeric_limits, kodenya hampir sama. Saya kira itu adalah cara umum untuk memiliki kelas enum yang aman-tipe. Saya berpendapat bahwa menggunakan numeric_limits lebih baik daripada meletakkan SENTINEL di akhir setiap enum.
Omair

1
Itu adalah bitet besar !
Lightness Races in Orbit

(berpotensi ...)
Lightness Races di Orbit

5

Jika kompiler Anda belum mendukung enums yang sangat diketik, Anda dapat melihat artikel berikut dari sumber c ++:

Dari abstrak:

Artikel ini menyajikan solusi untuk masalah membatasi operasi bit untuk
memungkinkan hanya operasi yang aman dan sah, dan mengubah semua manipulasi bit yang tidak valid menjadi kesalahan waktu kompilasi. Yang terbaik dari semuanya, sintaks operasi bit tetap tidak berubah, dan kode yang bekerja dengan bit tidak perlu dimodifikasi, kecuali kemungkinan untuk memperbaiki kesalahan yang belum terdeteksi.


5

Saya menggunakan makro berikut:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

Ini mirip dengan yang disebutkan di atas tetapi memiliki beberapa perbaikan:

  • Ini adalah tipe yang aman (tidak mengira bahwa tipe yang mendasarinya adalah int)
  • Tidak perlu menentukan secara manual jenis yang mendasarinya (sebagai lawan dari jawaban @LunarEclipse)

Itu perlu menyertakan type_traits:

#include <type_traits>

4

Saya ingin menguraikan jawaban Uliwitness , memperbaiki kodenya untuk C ++ 98 dan menggunakan idiom Safe Bool , karena kurangnya std::underlying_type<>template dan explicitkata kunci dalam versi C ++ di bawah C ++ 11.

Saya juga memodifikasinya sehingga nilai enum dapat berurutan tanpa penugasan eksplisit, sehingga Anda dapat memilikinya

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Anda kemudian bisa mendapatkan nilai bendera mentah dengan

seahawk.flags.value();

Ini kodenya.

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};

3

Berikut adalah opsi untuk bitmasks jika Anda tidak benar-benar menggunakan nilai enum individu (mis. Anda tidak perlu mematikannya) ... dan jika Anda tidak khawatir tentang mempertahankan kompatibilitas biner yaitu: Anda tidak peduli di mana bit Anda tinggal ... yang mungkin Anda berada. Anda juga sebaiknya tidak terlalu peduli dengan pelingkupan dan kontrol akses. Hmmm, enum memiliki beberapa properti bagus untuk bidang-bit ... ingin tahu apakah ada yang pernah mencobanya :)

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Kita dapat melihat bahwa hidup ini hebat, kita memiliki nilai-nilai tersendiri, dan kita memiliki int bagus untuk & dan | untuk isi hati kita, yang masih memiliki konteks apa artinya bit. Semuanya konsisten dan dapat diprediksi ... bagi saya ... selama saya tetap menggunakan kompiler VC ++ Microsoft dengan Pembaruan 3 pada Win10 x64 dan jangan menyentuh bendera kompiler saya :)

Meskipun semuanya hebat ... kami memiliki beberapa konteks mengenai arti bendera sekarang, karena ini bersatu dengan bitfield di dunia nyata yang mengerikan di mana program Anda mungkin bertanggung jawab atas lebih dari satu tugas terpisah yang dapat Anda lakukan masih secara tidak sengaja (cukup mudah) menghancurkan dua bidang bendera dari serikat yang berbeda bersama-sama (katakanlah, AnimalProperties dan ObjectProperties, karena keduanya int), mencampur semua bit milik Anda, yang merupakan bug mengerikan untuk dilacak ... dan bagaimana saya tahu banyak orang di pos ini tidak terlalu sering menggunakan bitmask, karena membangunnya mudah dan mempertahankannya sulit.

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Jadi, Anda menjadikan deklarasi serikat pribadi untuk mencegah akses langsung ke "Bendera", dan harus menambahkan getter / setter dan kelebihan operator, lalu buat makro untuk semua itu, dan Anda pada dasarnya segera kembali ke tempat Anda memulai ketika Anda mencoba lakukan ini dengan Enum.

Sayangnya jika Anda ingin kode Anda portabel, saya tidak berpikir ada cara untuk A) menjamin tata letak bit atau B) menentukan tata letak bit pada waktu kompilasi (sehingga Anda dapat melacaknya dan setidaknya mengoreksi perubahan di seluruh versi / platform dll) Diimbangi dalam struct dengan bidang bit

Saat runtime Anda dapat memainkan trik dengan mengatur bidang dan XOR bendera untuk melihat bit mana yang berubah, terdengar sangat jelek bagi saya meskipun ayat memiliki 100% konsisten, platform independen, dan solusi sepenuhnya deterministik yaitu: ENUM.

TL; DR: Jangan dengarkan para pembenci. C ++ bukan bahasa Inggris. Hanya karena definisi literal dari kata kunci yang disingkat yang diwarisi dari C mungkin tidak sesuai dengan penggunaan Anda, tidak berarti Anda tidak boleh menggunakannya ketika definisi C dan C ++ dari kata kunci tersebut benar-benar mencakup case use Anda. Anda juga dapat menggunakan struct untuk memodelkan hal-hal selain struktur, dan kelas untuk hal-hal selain sekolah dan kasta sosial. Anda bisa menggunakan float untuk nilai yang di-ground. Anda dapat menggunakan karakter untuk variabel yang tidak hangus atau orang dalam novel, drama, atau film. Setiap programmer yang masuk ke kamus untuk menentukan arti kata kunci sebelum spesifikasi bahasa adalah ... yah saya akan menahan lidah saya di sana.

Jika Anda ingin kode Anda dimodelkan setelah bahasa yang diucapkan Anda sebaiknya menulis di Objective-C, yang notabene juga menggunakan enum banyak untuk bitfield.


3

Hanya gula sintaksis. Tidak ada metadata tambahan.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

Operator bendera pada tipe integral hanya berfungsi.


IMHO ini adalah jawaban terbaik. Sintaks klien bersih dan sederhana, mudah. Saya hanya akan menggunakan "const int" daripada "constexpr uint8_t", tetapi konsepnya sama.
yoyo

(maaf, "constexpr int")
yoyo

3

Saat ini tidak ada dukungan bahasa untuk flag enum, kelas Meta mungkin menambahkan fitur ini secara inheren jika itu akan menjadi bagian dari standar c ++.

Solusi saya adalah membuat fungsi template instantiated enum-only menambahkan dukungan untuk operasi bitwise tipe-aman untuk kelas enum menggunakan tipe yang mendasarinya:

File: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Untuk kenyamanan dan untuk mengurangi kesalahan, Anda mungkin ingin membungkus operasi flag bit Anda untuk enum dan untuk integer juga:

File: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Kemungkinan penggunaan:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}

3

@Xaqq telah memberikan cara aman yang sangat bagus untuk menggunakan flag enum di sini oleh aflag_set kelas.

Saya menerbitkan kode di GitHub , penggunaannya adalah sebagai berikut:

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}

2

Anda membingungkan objek dan koleksi objek. Secara khusus, Anda mengacaukan flag biner dengan set flag biner. Solusi yang tepat akan terlihat seperti ini:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};

2

Inilah solusi saya tanpa perlu banyak kelebihan atau casting:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Saya pikir tidak apa-apa, karena kami mengidentifikasi enum dan int (tidak diketik dengan kuat).

Sama seperti catatan (lebih lama), jika Anda

  • ingin menggunakan enums yang sangat diketik dan
  • tidak perlu mengutak-atik bendera Anda
  • kinerja bukan masalah

Saya akan datang dengan ini:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

menggunakan daftar inisialisasi C ++ 11 dan enum class.


Ngomong-ngomong, saya lebih suka tidak merekomendasikan enums untuk bendera sama sekali. Alasan sederhana: kombinasi bendera bukan elemen enum lagi. Jadi ini sepertinya tidak cocok. Atau saya akan menggunakan using Flags = unsigned longdi dalam namespace atau struct yang berisi nilai-nilai bendera sendiri sebagai /*static*/ const Flags XY = 0x01dan seterusnya.
yau

1

Seperti di atas (Kai) atau lakukan hal berikut. Benar-benar enum adalah "Enumerasi", yang ingin Anda lakukan adalah memiliki set, oleh karena itu Anda harus benar-benar menggunakan stl :: set

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}

1

Mungkin seperti NS_OPTIONS dari Objective-C.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.

Bisakah Anda menjelaskan mengapa jawaban Anda paling cocok? Ada beberapa jawaban lain yang telah menjawab pertanyaan ini, jadi harap sertakan beberapa informasi untuk membedakan Anda.
trevorp
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.