จะใช้ enums เป็นค่าสถานะใน C ++ ได้อย่างไร


187

การรักษาenums ในฐานะธงทำงานได้ดีใน C # ผ่านทาง[Flags]ททริบิวต์ แต่วิธีที่ดีที่สุดในการทำ C ++ คืออะไร

ตัวอย่างเช่นฉันต้องการเขียน:

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

seahawk.flags = CanFly | EatsFish | Endangered;

อย่างไรก็ตามฉันได้รับข้อผิดพลาดเกี่ยวกับคอมไพเลอร์int/ enumการแปลง มีวิธีที่ดีกว่าในการแสดงนี้มากกว่าเพียงแค่การขว้างทื่อหรือไม่? โดยเฉพาะอย่างยิ่งฉันไม่ต้องการพึ่งพาโครงสร้างจากห้องสมุดบุคคลที่สามเช่น boost หรือ Qt

แก้ไข: ตามที่ระบุไว้ในคำตอบที่ฉันสามารถหลีกเลี่ยงการรวบรวมข้อผิดพลาดด้วยการประกาศเป็นseahawk.flags intอย่างไรก็ตามฉันต้องการมีกลไกในการบังคับใช้ความปลอดภัยประเภทดังนั้นบางคนไม่สามารถเขียนseahawk.flags = HasMaximizeButtonได้


เท่าที่ฉันทราบใน Visual C ++ 2013 [Flags]คุณลักษณะใช้งานได้ดีเช่น:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov ไม่ไม่ทำงานกับ C ++ (เช่นกัน) คุณหมายถึง C # หรือ
Ajay

5
@rivanov แอตทริบิวต์ [Flags] ทำงานได้เฉพาะกับ. Net Framework ใน C ++ CLI ดั้งเดิม C ++ ไม่สนับสนุนแอตทริบิวต์ดังกล่าว
Zoltan Tirinda

คำตอบ:


250

วิธี "ถูกต้อง" คือการกำหนดตัวดำเนินการบิตสำหรับ enum เป็น:

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));
}

ส่วนที่เหลือของตัวดำเนินการบิต แก้ไขตามต้องการหากช่วง enum เกินช่วง int


42
^ นี่ คำถามเดียวคือวิธีการทำให้เป็นอัตโนมัติ / เทมเพลตคำจำกัดความของโอเปอเรเตอร์เพื่อให้คุณไม่จำเป็นต้องกำหนดคำเหล่านั้นทุกครั้งที่คุณเพิ่ม enum ใหม่
eodabash

10
นอกจากนี้การร่ายจาก int โดยพลการกลับไปยังชนิด enum นั้นใช้ได้แม้ว่าค่า int จะไม่ตรงกับตัวระบุใด ๆ ของ enum หรือไม่
Ingo Schalk-Schupp

8
นี่เป็นเรื่องไร้สาระที่สมบูรณ์ สมาชิกคนใดที่AnimalFlagsเป็นตัวแทนจากการแสดงออกHasClaws | CanFly? นี่ไม่ใช่สิ่งที่enumมีไว้สำหรับ ใช้จำนวนเต็มและค่าคงที่
การแข่งขัน Lightness ใน Orbit

26
@LightnessRacesinOrbit: ไม่ถูกต้อง โดเมนประเภท enum เป็นโดเมนของประเภทพื้นฐาน - เป็นเพียงที่บางคนได้รับชื่อ และเพื่อตอบคำถามของคุณ: สมาชิก " (HasClaws | CanFly)"
Xeo

5
@MarcusJ: การ จำกัด ค่าของคุณเป็น 2 กำลังจะอนุญาตให้คุณใช้ enums เป็นบิตแฟล็ก ดังนั้นถ้าคุณได้ 3 คุณรู้ทั้งHasClaws(= 1) และCanFly(= 2) แต่หากคุณเพียงแค่กำหนดค่าที่ 1 ถึง 4 ตรงผ่านและคุณได้รับ 3 ก็อาจจะเป็นหนึ่งเดียวEatsFishหรืออีกครั้งรวมกันของและHasClaws CanFlyหากการแจงนับของคุณแสดงสถานะพิเศษเฉพาะค่าที่อยู่ติดกันนั้นก็ดี แต่การรวมกันของแฟล็กต้องการค่าที่จะเป็นบิตพิเศษ
Christian Severin

122

หมายเหตุ (นอกจากนี้ยังเป็นหัวข้อที่ค่อนข้างซับซ้อน): วิธีการสร้างแฟล็กที่ไม่ซ้ำใครสามารถทำได้โดยใช้การเลื่อนบิต ฉันเองพบว่าอ่านง่ายกว่านี้

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

สามารถเก็บค่าได้ถึง int ดังนั้นส่วนใหญ่แล้ว 32 ธงซึ่งสะท้อนอย่างชัดเจนในจำนวนกะ


2
คุณกรุณาลบเครื่องหมายจุลภาคสุดท้าย (3,) และเพิ่มเครื่องหมายโคลอนหลัง} เพื่อให้รหัสง่ายต่อการคัดลอกและวาง ขอบคุณ
Katu

4
ไม่มีการกล่าวถึงเลขฐานสิบหก? ดูหมิ่น!
Pharap

1
@Jamie, พระคาร์ดินัลเริ่มต้นด้วย 1 เสมออันดับแรกอาจเริ่มต้นด้วย 0 หรือ 1 ขึ้นอยู่กับว่าคุณกำลังคุยอยู่กับใคร
Michael

2
@Michael นั่นจริง! ใน enum คุณมักจะสำรอง 0 สำหรับ BLAH_NONE :-) ขอบคุณสำหรับการสั่นสะเทือนความทรงจำนั้น!
Jamie

1
@Katu •เครื่องหมายจุลภาคที่ฟุ่มเฟือยในการแจงนับขั้นสุดท้ายได้รับอนุญาตตามมาตรฐาน ฉันไม่ชอบ แต่ฉันรู้อยู่แล้วว่า Stroustrup จะบอกอะไรกับฉัน ... "คุณไม่ชอบหรือไม่รู้สึกดีที่จะสร้างภาษาของคุณเองฉันทำได้"
Eljay

55

สำหรับคนขี้เกียจอย่างฉันนี่คือวิธีแก้ปัญหาในการคัดลอกและวาง:

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 ความเกียจคร้านเป็นหนึ่งในสามคุณประโยชน์ที่ดีของโปรแกรมเมอร์: threevirtues.com
Pharap

10
นี่เป็นคำตอบที่ดีมากโปรดระวังว่ามันจะให้การทำงานระดับบิตอย่างสนุกสนานสำหรับทุกประเภท ฉันใช้บางอย่างที่คล้ายกัน แต่ด้วยการเพิ่มคุณสมบัติที่ระบุประเภทที่ฉันต้องการให้มันใช้รวมกับเวทมนตร์ enable_if เล็กน้อย
เชียงราย

@Rai: คุณสามารถวางไว้ใน namespace และมันที่เหมาะสมเช่นเดียวกับusing rel_ops
Yakov Galka

1
@ybungalobill แต่คุณยังคงมีปัญหาเดียวกันกับการดำเนินการที่ใช้กับประเภทใด ๆ ในขอบเขตการใช้งานซึ่งน่าจะตรงกับ enum หรือไม่ ฉันคิดว่าลักษณะเป็นสิ่งที่จำเป็นมากที่สุด
เชียงราย

19
อย่าใช้รหัสนี้ มันเปิดประตูสำหรับชั้นเรียนใด ๆ ที่จะดำเนินการโดยไม่ได้ตั้งใจ นอกจากนี้ยังมีการใช้รหัสหล่อแบบเก่าซึ่งจะไม่ผ่าน GCC เข้มงวดรวบรวมshitalshah.com/p/...
Shital Shah

44

หมายเหตุหากคุณทำงานในสภาพแวดล้อม Windows จะมีDEFINE_ENUM_FLAG_OPERATORSมาโครที่กำหนดไว้ใน winnt.h ซึ่งทำงานให้คุณ ดังนั้นในกรณีนี้คุณสามารถทำได้:

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

seahawk.flags = CanFly | EatsFish | Endangered;

44

ตัวแปร seahawk.flags ประเภทใด

ใน C ++ มาตรฐานการแจกแจงไม่ปลอดภัย พวกมันเป็นจำนวนเต็มอย่างมีประสิทธิภาพ

AnimalFlags ไม่ควรเป็นประเภทของตัวแปรของคุณ ตัวแปรของคุณควรอยู่ภายในและข้อผิดพลาดจะหายไป

การใส่ค่าเลขฐานสิบหกเหมือนที่คนอื่นแนะนำไม่จำเป็น มันไม่ต่างอะไรเลย

ค่า enum เป็นชนิด int ตามค่าเริ่มต้น ดังนั้นคุณสามารถ bitwise หรือรวมพวกมันเข้าด้วยกันและเก็บผลลัพธ์ไว้ใน int

ชนิด enum เป็นชุดย่อยที่ จำกัด ของ int ซึ่งค่าเป็นหนึ่งในค่าที่แจกแจง ดังนั้นเมื่อคุณสร้างค่าใหม่นอกช่วงนั้นคุณจะไม่สามารถกำหนดค่าได้โดยไม่ต้องเลือกตัวแปร enum ของคุณ

คุณสามารถเปลี่ยนประเภทค่า enum ได้หากต้องการ แต่ไม่มีประเด็นสำหรับคำถามนี้

แก้ไข:โปสเตอร์กล่าวว่าพวกเขาเกี่ยวข้องกับความปลอดภัยของประเภทและพวกเขาไม่ต้องการคุณค่าที่ไม่ควรอยู่ในประเภท int

แต่มันจะเป็นประเภทที่ไม่ปลอดภัยในการใส่ค่าที่อยู่นอกช่วงของ AnimalFlags ภายในตัวแปรประเภท AnimalFlags

มีวิธีที่ปลอดภัยในการตรวจสอบค่านอกช่วงแม้ว่าในประเภท 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
};

ด้านบนไม่ได้หยุดคุณจากการใส่ค่าสถานะที่ไม่ถูกต้องจาก enum อื่นที่มีค่า 1,2,4 หรือ 8

ถ้าคุณต้องการความปลอดภัยแบบสัมบูรณ์คุณก็สามารถสร้าง std :: set และเก็บแต่ละ flag ไว้ข้างในได้ มันไม่ได้เป็นพื้นที่ที่มีประสิทธิภาพ แต่มันก็ปลอดภัยและให้ความสามารถเช่นเดียวกับ bitflag int

C ++ 0x หมายเหตุ: พิมพ์ enums อย่างมาก

ใน C ++ 0x ในที่สุดคุณก็สามารถพิมพ์ค่า enum ที่ปลอดภัยได้ในที่สุด

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

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

4
ค่า enum ไม่ใช่จำนวนเต็ม แต่แปลงได้ง่ายเป็นจำนวนเต็ม ประเภทของHasClaws | CanFlyเป็นประเภทจำนวนเต็มบางส่วน แต่ประเภทHasClawsคือAnimalFlagsไม่ใช่ประเภทจำนวนเต็ม
Karu

1
อา แต่ถ้าเรากำหนดช่วงที่ถูกต้องของ enum ไม่ใช่แค่ค่าแฟล็กแต่ละตัว จากนั้นคำตอบของ eidolon นั้นถูกต้องและยืนยันว่ามีเพียงชุดค่าผสมของ enum ที่ถูกต้องเท่านั้นที่สามารถส่งผ่านเป็นประเภทนั้นได้
สกอตต์

3
@Scott: มันเป็นเรื่องน่าสังเกตว่ามาตรฐาน C ++ กำหนดช่วงที่ถูกต้องของค่าของตัวอย่างเช่น enum "สำหรับการแจงนับเมื่อ emin เป็นตัวแจงนับที่เล็กที่สุดและ emax ใหญ่ที่สุดค่าของการแจงนับเป็นค่าในช่วง bmin ถึง bmax, กำหนดดังนี้: ให้ K เป็น 1 แทนการเติมเต็มสองส่วนและ 0 สำหรับอันหนึ่ง ' การเติมเต็มหรือการแสดงความหมายของสัญญาณ bmax เป็นค่าที่เล็กที่สุดที่มากกว่าหรือเท่ากับmax(|emin| − K, |emax|)และเท่ากับ(1u<<M) - 1ซึ่งMเป็นจำนวนเต็มที่ไม่เป็นลบ "
Ben Voigt

สำหรับผู้ที่ (เช่นฉัน) เพียงต้องการบางสิ่งบางอย่างในทางปฏิบัติที่ช่วยให้ค่า Enum ถูกจัดการในระดับบิตและไม่ดูน่าเกลียดเกินไปกับเทมเพลตและการคัดเลือกนักแสดงประเภทนี่เป็นทางออกที่ดี intเพียงแค่กำหนดตัวแปรที่จะเป็นประเภท
Eric Sokolowsky

นอกจากนี้ยังทราบว่าใน C ++ ปกติenumไม่ได้ในทางเทคนิคเริ่มต้นที่จะintเป็นชนิดพื้นฐานของ (ทั้ง pre-C ++ 11 (IIRC) หรือโพสต์-C ++ 11 เมื่อไม่มีประเภทต้นแบบที่มีการระบุไว้) แม้จะไม่enum class แต่ประเภทพื้นฐานที่เป็นค่าเริ่มต้นจะเป็นสิ่งที่มีขนาดใหญ่พอที่จะเป็นตัวแทนของผู้แจงนับทั้งหมดโดยมีกฎฮาร์ดจริงเพียงข้อเดียวที่ใหญ่กว่าเท่านั้นintหากต้องการให้ชัดเจน โดยพื้นฐานแล้วประเภทพื้นฐานจะถูกระบุเป็น (ถอดความ) "สิ่งใดก็ได้ แต่อาจเป็นไปได้ intเว้นแต่ว่าตัวแจงนับใหญ่เกินไปสำหรับint"
Justin Time - Reinstate Monica

26

ฉันพบว่าคำตอบที่ยอมรับโดยeidolonอันตรายเกินไป เครื่องมือเพิ่มประสิทธิภาพของคอมไพเลอร์อาจตั้งสมมติฐานเกี่ยวกับค่าที่เป็นไปได้ใน enum และคุณอาจได้รับขยะกลับด้วยค่าที่ไม่ถูกต้อง และมักจะไม่มีใครต้องการที่จะกำหนดวิธีเรียงสับเปลี่ยนที่เป็นไปได้ทั้งหมดในธง enums

ดังที่ Brian R. Bondy ระบุไว้ด้านล่างหากคุณใช้ C ++ 11 (ซึ่งทุกคนควรมีก็ถือว่าดี) คุณสามารถทำสิ่งนี้ได้ง่ายขึ้นด้วย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. 

สิ่งนี้ช่วยให้แน่ใจว่ามีขนาดและช่วงค่าที่มั่นคงโดยการระบุประเภทสำหรับ enum, ยับยั้งการ downcasting อัตโนมัติของ enums ถึง ints ฯลฯ โดยการใช้enum classและใช้constexprเพื่อให้แน่ใจว่าโค้ดสำหรับตัวดำเนินการได้รับการอินไลน์

สำหรับคนที่ติดอยู่กับภาษาถิ่น 11-C ++

หากฉันติดอยู่กับคอมไพเลอร์ที่ไม่รองรับ C ++ 11 ฉันจะใช้การห่อแบบ int ในคลาสที่อนุญาตให้ใช้ตัวดำเนินการระดับบิตเท่านั้นและประเภทจาก enum นั้นเพื่อตั้งค่า:

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;
};

คุณสามารถกำหนดสิ่งนี้คล้ายกับ enum + typedef ปกติ:

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

typedef SafeEnum<enum TFlags_>  TFlags;

และการใช้งานก็คล้ายกันเช่นกัน:

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;

และคุณยังสามารถแทนที่ประเภทพื้นฐานสำหรับ enums ไบนารีที่มีเสถียรภาพ (เช่น C ++ 11 enum foo : type) typedef SafeEnum<enum TFlags_,uint8_t> TFlags;โดยใช้พารามิเตอร์แม่แบบที่สองคือ

ฉันทำเครื่องหมายการoperator boolแทนที่ด้วยexplicitคำหลักของ C ++ 11 เพื่อป้องกันไม่ให้เกิดการแปลง int เนื่องจากอาจทำให้ชุดค่าสถานะสิ้นสุดลงเป็น 0 หรือ 1 เมื่อเขียนออกมา ถ้าคุณไม่สามารถใช้ภาษา C ++ 11 (myFlags & EFlagTwo) == EFlagTwoออกเกินที่ออกและเขียนเงื่อนไขแรกในการใช้งานเช่นเป็น


ตามบันทึกฉันขอแนะนำให้ผู้ประกอบการตัวอย่างที่กำหนดไว้ที่การใช้งานเริ่มต้นstd::underlying_typeแทนการเข้ารหัสแบบเจาะจงเฉพาะหรือการระบุประเภทพื้นฐานและใช้เป็นนามแฝงประเภทแทนโดยตรง ด้วยวิธีนี้การเปลี่ยนแปลงประเภทพื้นฐานจะเผยแพร่โดยอัตโนมัติแทนที่จะต้องทำด้วยตนเอง
Justin Time - Reinstate Monica

17

วิธีที่ง่ายที่สุดที่จะทำเช่นนี้แสดงให้เห็นที่นี่โดยใช้มาตรฐานระดับห้องสมุดbitset

ในการเลียนแบบคุณลักษณะ C # ด้วยวิธีที่ปลอดภัยต่อประเภทคุณจะต้องเขียน wrapper เทมเพลตรอบ ๆ ชุดบิตโดยแทนที่อาร์กิวเมนต์ int ด้วย enum ที่กำหนดเป็นพารามิเตอร์ type ให้กับเทมเพลต สิ่งที่ต้องการ:

    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
ดูที่นี่สำหรับรหัสที่สมบูรณ์มากขึ้น: codereview.stackexchange.com/questions/96146/…
Shital Shah

11

ในความคิดของฉันไม่มีคำตอบที่เหมาะ เป็นอุดมคติฉันคาดหวังวิธีแก้ปัญหา:

  1. สนับสนุน==, !=, =, &, &=, |, |=และ~ผู้ประกอบการในความรู้สึกแบบเดิม (คือa & b)
  2. เป็นประเภทที่ปลอดภัยนั่นคือไม่อนุญาตให้มีการกำหนดค่าที่ไม่ได้ระบุเช่นตัวอักษรหรือประเภทจำนวนเต็ม (ยกเว้นการรวมบิตของค่าที่แจกแจง) หรืออนุญาตให้ตัวแปร enum ถูกกำหนดให้เป็นประเภทจำนวนเต็ม
  3. อนุญาตการแสดงออกเช่น if (a & b)...
  4. ไม่จำเป็นต้องมีมาโครที่ชั่วร้ายการใช้งานคุณสมบัติเฉพาะหรือแฮ็กอื่น ๆ

การแก้ปัญหาส่วนใหญ่ที่ผ่านมาในจุดที่ 2 หรือ 3 WebDancer คือการปิดในความคิดของฉัน แต่ล้มเหลวที่จุด 3 และต้องทำซ้ำสำหรับทุก enum

วิธีแก้ปัญหาที่ฉันเสนอคือ WebDancer เวอร์ชันทั่วไปที่ระบุจุด 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;
}

สิ่งนี้สร้างตัวดำเนินการโอเวอร์โหลดของตัวดำเนินการที่จำเป็น แต่ใช้ SFINAE เพื่อ จำกัด ประเภทตัวระบุ โปรดทราบว่าในความสนใจของความกะทัดรัดที่ผมยังไม่ได้กำหนดทั้งหมดของผู้ประกอบการ &แต่เพียงคนเดียวที่มีความแตกต่างกันคือ ขณะนี้โอเปอเรเตอร์เป็นส่วนกลาง (เช่นใช้กับประเภทที่แจกแจงทั้งหมด) แต่สิ่งนี้สามารถลดลงได้ด้วยการวางโอเวอร์โหลดในเนมสเปซ (สิ่งที่ฉันทำ) หรือโดยการเพิ่มเงื่อนไข SFINAE เพิ่มเติม (อาจใช้ประเภทพื้นฐานพิเศษ ) นี่underlying_type_tเป็นฟีเจอร์ C ++ 14 แต่ดูเหมือนว่าจะได้รับการสนับสนุนเป็นอย่างดีและง่ายต่อการลอกเลียนแบบสำหรับ C ++ 11 ด้วยวิธีง่าย ๆtemplate<typename T> using underlying_type_t = underlying_type<T>::type;


ในขณะที่วิธีแก้ปัญหาที่คุณเสนอนั้นใช้งานได้ดี แต่ก็ยังแนะนำรูปแบบนี้สำหรับ enums ที่ไม่ได้หมายถึงให้ถือว่าเป็นธง นั่นอาจเป็นสาเหตุของการใช้มาโคร (ร้าย) เช่น DEFINE_ENUM_FLAG_OPERATORS จาก Microsoft
WebDancer

@ WebDancer คุณถูกต้องแน่นอน แต่แล้วฉันก็พูดไปแล้วว่าในคำตอบของฉัน ฉันยังแนะนำวิธีการแก้ไขปัญหาสองวิธี - วางไว้ในเนมสเปซหรือใช้เงื่อนไข SFINAE ที่ จำกัด ยิ่งขึ้น
เทรเวอร์

จุดของฉันคือถ้าคุณทำnamespace ที่แคบจริงๆ (เช่น namespace AllMyFlagEnums) หรือมีเงื่อนไข SFINAE ที่ในบางวิธีเลือกเพียงไม่กี่ enums รหัสที่แตกในใจของฉัน แทนที่จะเสี่ยงสิ่งนี้ฉันคัดลอกและวาง "เทมเพลตต้นฉบับเดิม" ซึ่งฉันเพิ่งแทนที่แล้วระบุชื่อและบางครั้งก็เป็นมาโคร "ร้าย" ฉันหวังว่าจะมีวิธีที่ดีกว่า
WebDancer

ประการแรกมันจะทำให้เกิดปัญหาหากที่อื่นในรหัสของคุณคุณต้องทำสิ่งใดสิ่งหนึ่งที่ตั้งใจจะหยุดเช่นกำหนดตัวอักษรจำนวนเต็มหรือองค์ประกอบจาก enum อื่น มิฉะนั้น enum ที่ปรับเปลี่ยนจะทำงานเหมือน enum ปกติเช่นองค์ประกอบไม่จำเป็นต้องเป็นพลังของทั้งสองและการมอบหมายการเปรียบเทียบและการดำเนินการระดับบิตจะทำงานตามปกติ หากคุณต้องกำหนดตัวอักษรหรือผสมจำนวนจริง ๆ คุณยังสามารถส่งสัญญาณได้อย่างชัดเจนพร้อมกับข้อดีเพิ่มเติมที่เจตนาของคุณจะชัดเจนยิ่งขึ้น ดังนั้นโอกาสที่จะได้ไม่จำเป็นต้องลดขอบเขต
เทรเวอร์

ประการที่สองแม้ว่าคุณจะต้องลดขอบเขต แต่เนมสเปซอาจไม่จำเป็นต้องแคบลง - แม้ว่ามันจะขึ้นอยู่กับสิ่งที่คุณกำลังทำอยู่ หากคุณกำลังทำงานกับไลบรารีอยู่แล้วบางทีคุณอาจมีรหัสของคุณที่ขึ้นอยู่กับ enums ในเนมสเปซแล้วรหัส enum จะไปในเนมสเปซเดียวกัน หากคุณต้องการพฤติกรรม enum สำหรับคลาส (บางทีคุณอาจต้องการใช้ enums เป็นอาร์กิวเมนต์เมธอดหรือตัวแปรสมาชิกของคลาส) จากนั้นให้ใส่รหัส enum ในคลาสเพื่อให้ได้ผลเหมือนกัน บรรทัดล่างคือคุณไม่จำเป็นต้องห่อ namespace รอบ ๆ เพียงแค่ enums - แม้ว่าคุณจะทำได้
เทรเวอร์

8

มาตรฐาน C ++ พูดถึงสิ่งนี้อย่างชัดเจนดูหัวข้อ "17.5.2.1.3 ประเภท Bitmask":

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

รับ "แม่แบบ" นี้คุณจะได้รับ:

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;
}

และคล้ายกันสำหรับผู้ประกอบการรายอื่น นอกจากนี้ให้สังเกตว่า "constexpr" ซึ่งเป็นสิ่งจำเป็นหากคุณต้องการให้คอมไพเลอร์สามารถรันเวลาคอมไพเลอร์

หากคุณใช้ C ++ / CLI และต้องการกำหนดให้กับสมาชิก enum ของคลาสอ้างอิงคุณต้องใช้การอ้างอิงการติดตามแทน:

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

หมายเหตุ: ตัวอย่างนี้ยังไม่เสร็จสมบูรณ์ให้ดูหัวข้อ "17.5.2.1.3 Bitmask types" สำหรับชุดตัวดำเนินการที่สมบูรณ์


6

ฉันพบว่าตัวเองถามคำถามเดียวกันและเกิดขึ้นกับโซลูชันพื้นฐาน C ++ 11 ทั่วไปคล้ายกับของ 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);
    }   
};

อินเทอร์เฟซสามารถปรับปรุงเพื่อลิ้มรส จากนั้นจึงสามารถใช้งานได้เช่น:

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

2
ดูที่นี่สำหรับรหัสที่สมบูรณ์และดีขึ้น: codereview.stackexchange.com/questions/96146/…
Shital Shah

5
ยกเว้นการใช้ numeric_limits ของฉันรหัสเกือบเหมือนกัน ฉันคิดว่ามันเป็นวิธีการทั่วไปที่จะมีคลาส Enum ที่ปลอดภัย ฉันจะยืนยันว่าการใช้ numeric_limits นั้นดีกว่าการใส่ SENTINEL ที่ส่วนท้ายของทุก enum
Omair

1
นั่นเป็นบิตเซ็ตขนาดใหญ่ !
การแข่งขัน Lightness ใน Orbit


5

หากคอมไพเลอร์ของคุณยังไม่รองรับ enums ที่พิมพ์ออกมามากคุณสามารถดูบทความต่อไปนี้ได้จากแหล่ง c ++:

จากนามธรรม:

บทความนี้นำเสนอวิธีแก้ไขปัญหาการบังคับใช้บิตเพื่อ
ให้ปลอดภัยและถูกต้องตามกฎหมายเท่านั้นและเปลี่ยนการจัดการบิตที่ไม่ถูกต้องทั้งหมดให้เป็นข้อผิดพลาดในการรวบรวม สิ่งที่ดีที่สุดไวยากรณ์ของการดำเนินการบิตยังคงไม่เปลี่ยนแปลงและรหัสการทำงานกับบิตไม่จำเป็นต้องมีการแก้ไขยกเว้นอาจจะแก้ไขข้อผิดพลาดที่ยังไม่ได้ตรวจพบ


5

ฉันใช้มาโครต่อไปนี้:

#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) ); }

มันคล้ายกับที่กล่าวถึงข้างต้น แต่มีการปรับปรุงหลายประการ:

  • มันเป็นประเภทที่ปลอดภัย (มันไม่คิดว่าประเภทพื้นฐานเป็นint)
  • มันไม่จำเป็นต้องระบุประเภทพื้นฐานด้วยตนเอง (ตรงข้ามกับคำตอบของ @LunarEclipse)

มันจำเป็นต้องมี type_traits:

#include <type_traits>

4

ฉันต้องการอธิบายอย่างละเอียดเกี่ยวกับคำตอบของ Uliwitnessแก้ไขรหัสของเขาสำหรับ C ++ 98 และใช้Safe Bool idiomเนื่องจากไม่มีstd::underlying_type<>แม่แบบและexplicitคำหลักในรุ่น C ++ ด้านล่าง C ++ 11

ฉันยังแก้ไขเพื่อให้ค่า enum สามารถเรียงลำดับได้โดยไม่ต้องมีการมอบหมายอย่างชัดเจนดังนั้นคุณสามารถมีได้

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

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

จากนั้นคุณสามารถรับค่าแฟล็ก raw ด้วย

seahawk.flags.value();

นี่คือรหัส

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

นี่คือตัวเลือกสำหรับ bitmasks ถ้าคุณไม่ได้ใช้ค่า enum แต่ละตัว (เช่นคุณไม่จำเป็นต้องปิดมัน) ... และถ้าคุณไม่กังวลเกี่ยวกับการรักษาความเข้ากันได้ของไบนารี: คุณ ไม่สนใจว่าบิตของคุณอยู่ที่ไหน ... ซึ่งคุณน่าจะเป็น นอกจากนี้คุณไม่ควรกังวลกับการกำหนดขอบเขตและการควบคุมการเข้าถึงมากเกินไป อืมมมมมีคุณสมบัติที่ดีสำหรับบิตฟิลด์ ... สงสัยว่ามีใครเคยลองบ้าง :)

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 :(";
    }
}

เราสามารถเห็นได้ว่าชีวิตนั้นยอดเยี่ยมเรามีค่านิยมที่ไม่ต่อเนื่องและเรามี int และ & | ต่อเนื้อหาในใจของเราซึ่งยังคงมีบริบทของความหมายของบิต ทุกอย่างสอดคล้องและคาดเดาได้ ... สำหรับฉัน ... ตราบใดที่ฉันยังคงใช้คอมไพเลอร์ VC ++ ของ Microsoft พร้อมอัปเดต 3 บน Win10 x64 และอย่าแตะแฟล็กคอมไพเลอร์ของฉัน :)

แม้ว่าทุกอย่างดี ... เรามีบางบริบทกับความหมายของธงในขณะนี้เนื่องจากในสหภาพ w / bitfield ในโลกแห่งความจริงที่น่ากลัวที่โปรแกรมของคุณอาจจะเป็นผู้รับผิดชอบมากกว่าเป็นงานที่ไม่ต่อเนื่องเดียวที่คุณสามารถทำได้ ยังคงบังเอิญ (ค่อนข้างง่าย) สแมชบฟิลด์สองธงของสหภาพที่แตกต่างกัน (พูด AnimalProperties และ ObjectProperties เนื่องจากทั้งคู่เป็น ints) ผสมบิตทั้งหมดของคุณซึ่งเป็นข้อผิดพลาดที่น่ากลัวในการติดตาม ... และฉันรู้ได้อย่างไร หลายคนในโพสต์นี้ไม่ได้ทำงานกับ bitmasks บ่อยนักเนื่องจากการสร้างมันง่ายและการบำรุงรักษามันยาก

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 :(
}

ดังนั้นคุณจึงประกาศสหภาพของคุณเป็นส่วนตัวเพื่อป้องกันการเข้าถึง "แฟล็ก" โดยตรงและต้องเพิ่ม getters / setters และโอเปอเรเตอร์โอเวอร์โหลดจากนั้นสร้างมาโครสำหรับสิ่งนั้นทั้งหมดและคุณกลับมาที่เดิมเมื่อคุณพยายาม ทำสิ่งนี้กับ Enum

น่าเสียดายถ้าคุณต้องการให้รหัสของคุณพกพาได้ฉันไม่คิดว่าจะมีวิธีใดที่จะ A) รับประกัน bit layout หรือ B) กำหนด bit layout ในเวลาที่คอมไพล์ (เพื่อให้คุณสามารถติดตามและอย่างน้อยก็ถูกต้องสำหรับการเปลี่ยนแปลง รุ่น / แพลตฟอร์ม ฯลฯ ) ออฟเซ็ตใน struct ที่มีฟิลด์บิต

ที่รันไทม์คุณสามารถเล่นลูกเล่นที่มีการตั้งค่าฟิลด์และ XORing ธงเพื่อดูว่าบิตใดเปลี่ยนแปลงไปเสียงที่เส็งเคร็งสำหรับฉันแม้ว่าข้อที่มีความสอดคล้องกัน 100% แพลตฟอร์มอิสระและโซลูชันที่กำหนดอย่างสมบูรณ์เช่น: ENUM

TL; DR: อย่าฟังความเกลียดชัง C ++ ไม่ใช่ภาษาอังกฤษ เพียงเพราะคำจำกัดความที่แท้จริงของคำสำคัญที่สืบทอดมาจาก C อาจไม่เหมาะกับการใช้งานของคุณไม่ได้หมายความว่าคุณไม่ควรใช้มันเมื่อ C และนิยาม C ++ ของคำหลักนั้นรวมถึงกรณีการใช้งานของคุณ นอกจากนี้คุณยังสามารถใช้ structs เพื่อทำสิ่งต่าง ๆ นอกเหนือจากโครงสร้างและคลาสสำหรับสิ่งอื่นที่ไม่ใช่โรงเรียนและวรรณะทางสังคม คุณอาจใช้โฟลตสำหรับค่าที่มีการต่อสายดิน คุณสามารถใช้อักขระพิเศษสำหรับตัวแปรที่ไม่ถูกเผาไหม้หรือบุคคลในนวนิยายละครหรือภาพยนตร์ โปรแกรมเมอร์ผู้ใดก็ตามที่ไปที่พจนานุกรมเพื่อกำหนดความหมายของคำสำคัญก่อนที่ข้อมูลจำเพาะภาษาจะเป็น ... เอาละฉันจะเก็บลิ้นของฉันไว้ที่นั่น

หากคุณต้องการให้โค้ดของคุณเป็นแบบอย่างหลังจากภาษาพูดคุณควรเขียนใน Objective-C อย่างดีที่สุดซึ่งบังเอิญใช้ enums เป็นอย่างมากสำหรับบิตฟิลด์


3

น้ำตาล syntactic เท่านั้น ไม่มีข้อมูลเมตาเพิ่มเติม

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;
}

ตัวดำเนินการตั้งค่าสถานะในชนิดที่รวมทำงาน


IMHO นี่คือคำตอบที่ดีที่สุด ไวยากรณ์ไคลเอนต์ที่สะอาดและเรียบง่ายและง่าย ฉันจะใช้ "const int" แทน "constexpr uint8_t" แต่แนวคิดนั้นเหมือนกัน
yoyo

(ขออภัย "constexpr int")
yoyo

3

ขณะนี้ไม่มีการสนับสนุนด้านภาษาสำหรับ enum flag คลาส Meta อาจเพิ่มคุณสมบัตินี้โดยเนื้อแท้หากเป็นส่วนหนึ่งของมาตรฐาน c ++

โซลูชันของฉันคือการสร้างฟังก์ชั่นแม่แบบ instantiated enum เท่านั้นเพิ่มการสนับสนุนสำหรับการดำเนินงานระดับบิตที่ปลอดภัยสำหรับประเภท enum โดยใช้ประเภทพื้นฐาน:

ไฟล์: 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_

เพื่อความสะดวกและเพื่อลดข้อผิดพลาดคุณอาจต้องการตัดการดำเนินการตั้งค่าสถานะบิตสำหรับ enums และจำนวนเต็มเช่นกัน:

ไฟล์: 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_

การใช้งานที่เป็นไปได้:

#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 มอบวิธีที่ปลอดภัยในการใช้ค่าสถานะ enum ที่นี่โดย aflag_setระดับ

ฉันเผยแพร่รหัสในGitHubการใช้งานมีดังนี้:

#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

คุณกำลังสับสนวัตถุและชุดของวัตถุ โดยเฉพาะอย่างยิ่งคุณกำลังสับสนธงไบนารีด้วยชุดของธงไบนารี วิธีแก้ปัญหาที่เหมาะสมจะมีลักษณะเช่นนี้:

// 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

นี่คือวิธีการแก้ปัญหาของฉันโดยไม่ต้องใช้การบรรทุกเกินพิกัดหรือการหล่อ:

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);
}

ฉันคิดว่ามันโอเคเพราะเราระบุ enums และ ints ที่ไม่ใช่ (พิมพ์อย่างรุนแรง)

เช่นเดียวกับบันทึกด้านข้าง (อีกต่อไป) ถ้าคุณ

  • ต้องการที่จะใช้ enums พิมพ์อย่างยิ่งและ
  • ไม่จำเป็นต้องเล่นหนักกับธงของคุณ
  • ประสิทธิภาพไม่ใช่ปัญหา

ฉันจะเกิดขึ้นกับสิ่งนี้:

#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);
}

โดยใช้ C ++ 11 enum classรายการและการเริ่มต้น


โดยวิธีการที่ฉันค่อนข้างจะไม่แนะนำ enums สำหรับธงเลย เหตุผลง่ายๆ: การรวมกันของธงไม่ใช่องค์ประกอบของ enum อีกครั้ง ดังนั้นนี่จึงดูไม่เหมาะสมเลยทีเดียว อีกทางหนึ่งฉันจะใช้using Flags = unsigned longภายใน namespace หรือ struct ที่มีค่าสถานะตัวเองเป็น/*static*/ const Flags XY = 0x01และอื่น ๆ
เหยา

1

ดังที่ได้กล่าวมา (ไค) หรือทำดังต่อไปนี้ enums จริงๆคือ "Enumerations" สิ่งที่คุณต้องการทำคือมี set ดังนั้นคุณควรใช้ 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

อาจจะชอบ NS_OPTIONS ของ 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.

คุณช่วยอธิบายได้ไหมว่าทำไมคำตอบของคุณจึงเหมาะสมที่สุด? มีคำตอบอื่น ๆ อีกมากมายที่ตอบคำถามนี้ดังนั้นโปรดรวมข้อมูลบางอย่างเพื่อแยกความแตกต่างของคุณ
trevorp
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.