การใช้ขอบเขตที่กำหนดสำหรับบิตแฟล็กใน C ++


60

enum X : int(C #) หรือenum class X : int(C ++ 11) เป็นชนิดที่มีเขตข้อมูลภายในที่ซ่อนของintที่สามารถเก็บค่าใด ๆ นอกจากนี้ค่าคงที่จำนวนหนึ่งที่กำหนดไว้ล่วงหน้าของXจะถูกกำหนดไว้ใน enum เป็นไปได้ที่จะส่งค่า enum ให้เป็นค่าจำนวนเต็มและในทางกลับกัน ทั้งหมดนี้เป็นจริงทั้งใน C # และ C ++ 11

ใน C # enums ไม่ได้ใช้เพียงเพื่อเก็บค่าของแต่ละบุคคล แต่ยังถืออยู่รวมกันค่าที่เหมาะสมของธงตามคำแนะนำของไมโครซอฟท์ เช่น enums (ปกติ แต่ไม่จำเป็น) ตกแต่งด้วย[Flags]แอตทริบิวต์ เพื่อทำให้ชีวิตของนักพัฒนาง่ายขึ้นผู้ประกอบการระดับบิต (OR, และอื่น ๆ ... ) จะได้รับการโหลดมากเกินไปเพื่อให้คุณสามารถทำสิ่งนี้ได้อย่างง่ายดาย (C #):

void M(NumericType flags);

M(NumericType.Sign | NumericType.ZeroPadding);

ฉันเป็นนักพัฒนา C # ที่มีประสบการณ์ แต่ได้เขียนโปรแกรม C ++ เพียงสองสามวันแล้วและตอนนี้ฉันยังไม่รู้จักกับการประชุม C ++ ฉันตั้งใจจะใช้ C ++ 11 enum ในลักษณะเดียวกับที่เคยทำใน C # ใน C ++ 11 ผู้ประกอบการระดับบิตบน enums ขอบเขตจะไม่มากเกินไปดังนั้นผมอยากจะเกินพวกเขา

สิ่งนี้เรียกร้องให้มีการถกเถียงกันและความคิดเห็นดูเหมือนจะแตกต่างกันระหว่างสามตัวเลือก:

  1. ตัวแปรชนิด enum ถูกใช้เพื่อเก็บฟิลด์บิตคล้ายกับ C #:

    void M(NumericType flags);
    
    // With operator overloading:
    M(NumericType::Sign | NumericType::ZeroPadding);
    
    // Without operator overloading:
    M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));

    แต่สิ่งนี้จะตอบโต้ปรัชญา enum ที่พิมพ์ออกมาอย่างมากของการกำหนดขอบเขตของ C ++ 11

  2. ใช้จำนวนเต็มธรรมดาหากคุณต้องการเก็บค่าจำนวนเต็มรวม enums:

    void M(int flags);
    
    M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));

    แต่สิ่งนี้จะลดทุกสิ่งทุกอย่างเป็น a intทำให้คุณไม่มีเงื่อนงำว่าคุณควรจะใช้วิธีการแบบไหน

  3. เขียนคลาสที่แยกจากกันซึ่งจะโอเวอร์โหลดโอเปอเรเตอร์และเก็บค่าสถานะ bitwise ในฟิลด์จำนวนเต็มที่ซ่อนอยู่:

    class NumericTypeFlags {
        unsigned flags_;
    public:
        NumericTypeFlags () : flags_(0) {}
        NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {}
        //...define BITWISE test/set operations
    };
    
    void M(NumericTypeFlags flags);
    
    M(NumericType::Sign | NumericType::ZeroPadding);

    ( รหัสเต็มโดยuser315052 )

    แต่คุณไม่มี IntelliSense หรือการสนับสนุนใด ๆ ที่จะบอกใบ้คุณถึงค่าที่เป็นไปได้

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

แน่นอนว่าวิธีการทั้งสามนี้ใช้ได้ผลฉันกำลังมองหาเหตุผลที่เป็นข้อเท็จจริงและเทคนิคการประชุมที่ยอมรับโดยทั่วไปไม่ใช่การตั้งค่าส่วนตัว

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


2
คณะกรรมการ ISO C ++ พบว่าตัวเลือกที่ 1 มีความสำคัญพอที่จะระบุอย่างชัดเจนว่าช่วงค่าของ enums รวมการรวมกันของค่าสถานะไบนารีทั้งหมด (สิ่งนี้มาก่อน C ++ 03) ดังนั้นจึงมีการอนุมัติอย่างเป็นทางการสำหรับคำถามที่ค่อนข้างเป็นอัตนัย
MSalters

1
(เพื่อชี้แจงความคิดเห็นของ @MSalters ช่วงของ C ++ enum นั้นขึ้นอยู่กับประเภทของมัน (ถ้าเป็นประเภทตายตัว) หรือเป็นอย่างอื่นของ enumerators ในกรณีหลังช่วงนั้นจะขึ้นอยู่กับ bitfield ที่เล็กที่สุดที่สามารถเก็บ enumerators ที่กำหนดไว้ทั้งหมด ; เช่นสำหรับenum E { A = 1, B = 2, C = 4, };ช่วงคือ0..7(3 บิต) ดังนั้นมาตรฐาน C ++ รับรองอย่างชัดเจนว่า # 1 จะเป็นตัวเลือกที่ทำงานได้ [โดยเฉพาะenum classค่าเริ่มต้นenum class : intเว้นแต่จะระบุไว้เป็นอย่างอื่นและมีประเภทพื้นฐานที่แน่นอน])
Justin เวลา

คำตอบ:


31

วิธีที่ง่ายที่สุดคือให้ผู้ใช้งานทำงานเกินตัวเอง ฉันกำลังคิดที่จะสร้างแมโครเพื่อขยายการโอเวอร์โหลดพื้นฐานต่อประเภท

#include <type_traits>

enum class SBJFrameDrag
{
    None = 0x00,
    Top = 0x01,
    Left = 0x02,
    Bottom = 0x04,
    Right = 0x08,
};

inline SBJFrameDrag operator | (SBJFrameDrag lhs, SBJFrameDrag rhs)
{
    using T = std::underlying_type_t <SBJFrameDrag>;
    return static_cast<SBJFrameDrag>(static_cast<T>(lhs) | static_cast<T>(rhs));
}

inline SBJFrameDrag& operator |= (SBJFrameDrag& lhs, SBJFrameDrag rhs)
{
    lhs = lhs | rhs;
    return lhs;
}

(โปรดทราบว่าtype_traitsเป็นส่วนหัว C ++ 11 และstd::underlying_type_tเป็นคุณสมบัติ C ++ 14)


6
std :: รากฐาน _type_t คือ C ++ 14 สามารถใช้ std :: รากฐาน _type <T> :: พิมพ์ใน C ++ 11
ddevienne

14
เหตุใดคุณจึงใช้static_cast<T>สำหรับการป้อนข้อมูล แต่การส่งแบบ C เพื่อผลลัพธ์ที่นี่
Ruslan

2
@Ruslan ฉันสองคำถามนี้
audiFanatic

ทำไมคุณถึงรบกวน std :: รากฐาน_type_tเมื่อคุณรู้ว่ามันเป็น int?
poizan42

1
หากSBJFrameDragมีการกำหนดไว้ในคลาสและ|มีการใช้ตัวดำเนินการในภายหลังในนิยามของคลาสเดียวกันคุณจะกำหนดตัวดำเนินการอย่างไรเพื่อให้สามารถใช้ภายในคลาสได้
HelloGoodbye

6

ในอดีตฉันมักจะใช้การแจกแจงแบบเก่า (แบบอ่อน) เพื่อตั้งชื่อบิตค่าคงที่และใช้คลาสหน่วยเก็บข้อมูลอย่างชัดเจนเพื่อเก็บค่าสถานะผลลัพธ์ ที่นี่ความรับผิดชอบจะอยู่กับฉันเพื่อให้แน่ใจว่าการแจกแจงของฉันเหมาะสมกับประเภทการจัดเก็บและติดตามการเชื่อมโยงระหว่างเขตข้อมูลและค่าคงที่ที่เกี่ยวข้อง

ฉันชอบแนวคิดของ enums ที่พิมพ์ออกมาอย่างรุนแรง แต่ฉันไม่พอใจกับความคิดที่ว่าตัวแปรประเภทที่ระบุอาจมีค่าที่ไม่อยู่ในค่าคงที่ของการแจงนับนั้น

เช่นสมมติว่า bitwise หรือโอเวอร์โหลด:

enum class E1 { A=1, B=2, C=4 };
void test(E1 e) {
    switch(e) {
    case E1::A: do_a(); break;
    case E1::B: do_b(); break;
    case E1::C: do_c(); break;
    default:
        illegal_value();
    }
}
// ...
test(E1::A); // ok
test(E1::A | E1::B); // nope

สำหรับตัวเลือกที่ 3 ของคุณคุณจำเป็นต้องมีสำเร็จรูปสำเร็จรูปเพื่อแยกประเภทการจัดเก็บของการแจงนับ สมมติว่าเราต้องการบังคับประเภทที่ไม่ได้ลงนาม (เราสามารถจัดการกับการเซ็นชื่อด้วยรหัสเพิ่มเติมอีกเล็กน้อย):

template <size_t Size> struct IntegralTypeLookup;
template <> struct IntegralTypeLookup<sizeof(int64_t)> { typedef uint64_t Type; };
template <> struct IntegralTypeLookup<sizeof(int32_t)> { typedef uint32_t Type; };
template <> struct IntegralTypeLookup<sizeof(int16_t)> { typedef uint16_t Type; };
template <> struct IntegralTypeLookup<sizeof(int8_t)>  { typedef uint8_t Type; };

template <typename IntegralType> struct Integral {
    typedef typename IntegralTypeLookup<sizeof(IntegralType)>::Type Type;
};

template <typename ENUM> class EnumeratedFlags {
    typedef typename Integral<ENUM>::Type RawType;
    RawType raw;
public:
    EnumeratedFlags() : raw() {}
    EnumeratedFlags(EnumeratedFlags const&) = default;

    void set(ENUM e)   { raw |=  static_cast<RawType>(e); }
    void reset(ENUM e) { raw &= ~static_cast<RawType>(e); };
    bool test(ENUM e) const { return raw & static_cast<RawType>(e); }

    RawType raw_value() const { return raw; }
};
enum class E2: uint8_t { A=1, B=2, C=4 };
typedef EnumeratedFlags<E2> E2Flag;

สิ่งนี้ยังคงไม่ให้ IntelliSense หรือการเติมข้อความอัตโนมัติของคุณ แต่การตรวจจับประเภทการจัดเก็บนั้นน่าเกลียดน้อยกว่าที่ฉันคาดไว้


ตอนนี้ฉันหาทางเลือกอื่น: คุณสามารถระบุประเภทการจัดเก็บสำหรับการแจงนับที่พิมพ์ไม่ได้ มันยังมีไวยากรณ์เดียวกับใน C #

enum E4 : int { ... };

เนื่องจากมันพิมพ์ได้อย่างอ่อนและแปลงโดยปริยายเป็น / จาก int (หรือประเภทการจัดเก็บใด ๆ ที่คุณเลือก) จึงรู้สึกแปลกน้อยกว่าที่จะมีค่าที่ไม่ตรงกับค่าคงที่ที่ระบุ

ข้อเสียคือสิ่งนี้อธิบายว่า "หัวต่อหัวเลี้ยว" ...

NB ตัวแปรนี้เพิ่มค่าคงที่ที่แจกแจงให้กับทั้งขอบเขตซ้อนและขอบเขตการล้อมรอบ แต่คุณสามารถหลีกเลี่ยงปัญหานี้ด้วยเนมสเปซ:

namespace E5 {
    enum Enum : int { A, B, C };
}
E5::Enum x = E5::A; // or E5::Enum::A

1
ข้อเสียอีกอย่างของ enums ที่พิมพ์อย่างอ่อนแอคือค่าคงที่ของพวกเขาทำให้เนมสเปซของฉันสกปรกเนื่องจากพวกเขาไม่จำเป็นต้องขึ้นต้นด้วยชื่อ enum และนั่นอาจทำให้เกิดพฤติกรรมแปลก ๆ ทุกชนิดถ้าคุณมีสมาชิกสองคนที่แตกต่างกันทั้งสองด้วยสมาชิกชื่อเดียวกัน
Daniel AA Pelsmaeker

นั่นเป็นเรื่องจริง ตัวแปรที่พิมพ์ไม่ได้ด้วยประเภทการจัดเก็บที่ระบุจะเพิ่มค่าคงที่ให้กับทั้งขอบเขตการปิดล้อมและขอบเขตของตัวเอง iiuc
ไร้ประโยชน์

ตัวแจงนับที่ไม่มีขอบเขตจะประกาศในขอบเขตโดยรอบเท่านั้น ความสามารถในการผ่านการรับรองโดย enum-name เป็นส่วนหนึ่งของกฎการค้นหาไม่ใช่การประกาศ C ++ 11 7.2 / 10: แต่ละ enum-name และแต่ละตัวแจงนับที่ไม่ได้กำหนดขอบเขตจะประกาศในขอบเขตที่มีตัวระบุ enum ทันที ตัวแจงนับที่กำหนดขอบเขตแต่ละอันจะถูกประกาศในขอบเขตของการแจงนับ ชื่อเหล่านี้ปฏิบัติตามกฎขอบเขตที่กำหนดไว้สำหรับชื่อทั้งหมดใน (3.3) และ (3.4)
Lars Viklund

1
ด้วย C ++ 11 เรามี std :: รากฐาน _type ที่จัดเตรียมประเภทพื้นฐานของ enum ดังนั้นเราจึงมี 'เทมเพลต <typename IntegralType> struct Integral {typedef typename std :: รากฐาน _type <IntegralType> :: type Type; }; `ใน C ++ 14 สิ่งเหล่านี้จะง่ายกว่า to'template <typename IntegralType> struct Integral {typedef std :: รากฐาน_type_t <IntegralType> Type; };
emsr

4

คุณสามารถกำหนดธง enum ชนิดปลอดภัยใน C ++ 11 std::enable_ifโดยใช้ นี่คือการใช้งานพื้นฐานที่อาจหายไปบางสิ่ง:

template<typename Enum, bool IsEnum = std::is_enum<Enum>::value>
class bitflag;

template<typename Enum>
class bitflag<Enum, true>
{
public:
  constexpr const static int number_of_bits = std::numeric_limits<typename std::underlying_type<Enum>::type>::digits;

  constexpr bitflag() = default;
  constexpr bitflag(Enum value) : bits(1 << static_cast<std::size_t>(value)) {}
  constexpr bitflag(const bitflag& other) : bits(other.bits) {}

  constexpr bitflag operator|(Enum value) const { bitflag result = *this; result.bits |= 1 << static_cast<std::size_t>(value); return result; }
  constexpr bitflag operator&(Enum value) const { bitflag result = *this; result.bits &= 1 << static_cast<std::size_t>(value); return result; }
  constexpr bitflag operator^(Enum value) const { bitflag result = *this; result.bits ^= 1 << static_cast<std::size_t>(value); return result; }
  constexpr bitflag operator~() const { bitflag result = *this; result.bits.flip(); return result; }

  constexpr bitflag& operator|=(Enum value) { bits |= 1 << static_cast<std::size_t>(value); return *this; }
  constexpr bitflag& operator&=(Enum value) { bits &= 1 << static_cast<std::size_t>(value); return *this; }
  constexpr bitflag& operator^=(Enum value) { bits ^= 1 << static_cast<std::size_t>(value); return *this; }

  constexpr bool any() const { return bits.any(); }
  constexpr bool all() const { return bits.all(); }
  constexpr bool none() const { return bits.none(); }
  constexpr operator bool() { return any(); }

  constexpr bool test(Enum value) const { return bits.test(1 << static_cast<std::size_t>(value)); }
  constexpr void set(Enum value) { bits.set(1 << static_cast<std::size_t>(value)); }
  constexpr void unset(Enum value) { bits.reset(1 << static_cast<std::size_t>(value)); }

private:
  std::bitset<number_of_bits> bits;
};

template<typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value, bitflag<Enum>>::type operator|(Enum left, Enum right)
{
  return bitflag<Enum>(left) | right;
}
template<typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value, bitflag<Enum>>::type operator&(Enum left, Enum right)
{
  return bitflag<Enum>(left) & right;
}
template<typename Enum>
constexpr typename std::enable_if_t<std::is_enum<Enum>::value, bitflag<Enum>>::type operator^(Enum left, Enum right)
{
  return bitflag<Enum>(left) ^ right;
}

โปรดทราบว่าnumber_of_bitsคอมไพเลอร์ไม่สามารถกรอกข้อมูลได้เนื่องจาก C ++ ไม่มีวิธีการใด ๆ ที่จะทำการไตร่ตรองค่าที่เป็นไปได้ของการแจงนับ

แก้ไข: ที่จริงฉันยืนแก้ไขมันเป็นไปได้ที่จะได้รับการรวบรวมคอมไพเลอร์number_of_bitsสำหรับคุณ

หมายเหตุสิ่งนี้สามารถจัดการ (ช่วงค่า enum ที่ไม่ต่อเนื่องอย่างไม่มีประสิทธิภาพ) สมมติว่าไม่ใช่ความคิดที่ดีที่จะใช้ข้างต้นกับ enum เช่นนี้หรือความบ้าคลั่งจะตามมา:

enum class wild_range { start = 0, end = 999999999 };

แต่ทุกสิ่งที่คิดว่านี่เป็นทางออกที่ใช้ได้ในที่สุด ไม่จำเป็นต้องมี bitfiddling ใช้ด้านเป็นชนิดที่ปลอดภัยและอยู่ในขอบเขตของมันเป็นที่มีประสิทธิภาพที่จะได้รับ (ฉันพิงมั่นในstd::bitsetคุณภาพการดำเนินงานที่นี่;))


ฉันแน่ใจว่าฉันพลาดโอเปอร์เรเตอร์บางตัว
rubenvb

2

ผม เกลียด มาโครเกลียดชังใน C ++ 14 ของฉันมากพอ ๆ กับผู้ชายคนถัดไป แต่ฉันได้ใช้มันไปทั่วสถานที่และค่อนข้างอิสระเช่นกัน:

#define ENUM_FLAG_OPERATOR(T,X) inline T operator X (T lhs, T rhs) { return (T) (static_cast<std::underlying_type_t <T>>(lhs) X static_cast<std::underlying_type_t <T>>(rhs)); } 
#define ENUM_FLAGS(T) \
enum class T; \
inline T operator ~ (T t) { return (T) (~static_cast<std::underlying_type_t <T>>(t)); } \
ENUM_FLAG_OPERATOR(T,|) \
ENUM_FLAG_OPERATOR(T,^) \
ENUM_FLAG_OPERATOR(T,&) \
enum class T

ทำให้ใช้ง่ายเหมือน

ENUM_FLAGS(Fish)
{
    OneFish,
    TwoFish,
    RedFish,
    BlueFish
};

และอย่างที่พวกเขาบอกว่าหลักฐานอยู่ในพุดดิ้ง:

ENUM_FLAGS(Hands)
{
    NoHands = 0,
    OneHand = 1 << 0,
    TwoHands = 1 << 1,
    LeftHand = 1 << 2,
    RightHand = 1 << 3
};

Hands hands = Hands::OneHand | Hands::TwoHands;
if ( ( (hands & ~Hands::OneHand) ^ (Hands::TwoHands) ) == Hands::NoHands)
{
    std::cout << "Look ma, no hands!" << std::endl;
}

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


2
หากคุณเกลียดแมโครมากทำไมไม่ใช้โครงสร้างC ++ ที่เหมาะสมและเขียนตัวดำเนินการเทมเพลตแทนแมโคร เนื้อหาเทมเพลตดีกว่าเพราะคุณสามารถใช้std::enable_ifกับstd::is_enumเพื่อ จำกัด การให้บริการโอเวอร์โหลดของคุณให้ทำงานกับประเภทที่ระบุเท่านั้น ฉันยังเพิ่มตัวดำเนินการเปรียบเทียบ (โดยใช้std::underlying_type) และตัวดำเนินการเชิงตรรกะเพื่อเชื่อมช่องว่างเพิ่มเติมโดยไม่สูญเสียการพิมพ์ที่รัดกุม สิ่งเดียวที่ฉันไม่สามารถเทียบได้คือการแปลงโดยนัยเป็นบูล แต่flags != 0และ!flagsเพียงพอสำหรับฉัน
monkey0506

1

โดยทั่วไปคุณจะต้องกำหนดชุดของค่าจำนวนเต็มที่สอดคล้องกับเลขฐานสองชุดบิตเดียวแล้วเพิ่มเข้าด้วยกัน นี่คือวิธีที่โปรแกรมเมอร์ C มักจะทำ

ดังนั้นคุณต้องมี (ใช้ตัวดำเนินการ bitshift เพื่อตั้งค่าเช่น 1 << 2 เหมือนกันกับไบนารี 100)

#define ENUM_1 1
#define ENUM_2 1 << 1
#define ENUM_3 1 << 2

ฯลฯ

ใน C ++ คุณมีตัวเลือกมากขึ้นให้กำหนดชนิดใหม่แทนที่จะเป็นแบบ int (ใช้typedef ) และตั้งค่าในทำนองเดียวกันดังนี้ หรือกำหนดbitfieldหรือเวกเตอร์ของ bools 2 อันสุดท้ายนั้นมีพื้นที่ว่างที่มีประสิทธิภาพและเหมาะสมสำหรับการจัดการกับธง bitfield มีข้อดีคือให้คุณตรวจสอบแบบ (และ intellisense)

ฉันว่า (ส่วนตัวเห็นได้ชัด) ว่าโปรแกรมเมอร์ C ++ ควรใช้ bitfield สำหรับปัญหาของคุณ แต่ฉันมักจะเห็นวิธี #define ที่ใช้โดยโปรแกรม C จำนวนมากในโปรแกรม C ++

ฉันคิดว่า bitfield นั้นใกล้เคียงกับ enum ของ C # มากขึ้นทำไม C # จึงพยายามที่จะให้ enum เป็นแบบ bitfield นั้นแปลกมาก - Enum ควรเป็นแบบ "single-select"


11
การใช้มาโครใน c ++ ในลักษณะนี้แย่มาก
BЈовић

3
C ++ 14 ช่วยให้คุณกำหนดตัวอักษรไบนารี (เช่น0b0100) ดังนั้น1 << nรูปแบบจึงล้าสมัย
Rob K

บางทีคุณอาจจะหมายBitsetแทน bitfield
Jorge Bellon

1

ตัวอย่างสั้น ๆ ของ enum-flag ด้านล่างมีลักษณะเหมือน C #

เกี่ยวกับวิธีการในความคิดของฉัน: รหัสน้อยข้อผิดพลาดน้อยกว่ารหัสที่ดีกว่า

#indlude "enum_flags.h"

ENUM_FLAGS(foo_t)
enum class foo_t
    {
     none           = 0x00
    ,a              = 0x01
    ,b              = 0x02
    };

ENUM_FLAGS(foo2_t)
enum class foo2_t
    {
     none           = 0x00
    ,d              = 0x01
    ,e              = 0x02
    };  

int _tmain(int argc, _TCHAR* argv[])
    {
    if(flags(foo_t::a & foo_t::b)) {};
    // if(flags(foo2_t::d & foo_t::b)) {};  // Type safety test - won't compile if uncomment
    };

ENUM_FLAGS (T) เป็นแมโครที่กำหนดไว้ในenum_flags.h (น้อยกว่า 100 บรรทัดสามารถใช้งานได้โดยไม่มีข้อ จำกัด )


1
เป็นไฟล์enum_flags.hเช่นเดียวกับในการแก้ไขครั้งที่ 1 ของคำถามของคุณ? หากใช่คุณสามารถใช้ URL การแก้ไขเพื่ออ้างอิง: http://programmers.stackexchange.com/revisions/205567/1
gnat

+1 ดูดีสะอาด ฉันจะลองในโครงการ SDK ของเรา
Garet Claborn

1
@GaretClaborn นี่คือสิ่งที่ฉันเรียกว่าสะอาด: paste.ubuntu.com/23883996
sehe

1
แน่นอนพลาดไปที่::typeนั่น คงที่: paste.ubuntu.com/23884820
2560

@ hehe เฮ้รหัสเทมเพลตไม่น่าจะอ่านง่ายและสมเหตุสมผล คาถานี้คืออะไร? ดี .... เป็นข้อมูลโค้ดนี้ที่เปิดใช้ lol
Garet Claborn

0

ยังมีอีกวิธีในการทำให้แมว:

อย่างน้อยบางคนอาจต้องการเพิ่มสายการบิน 4 เส้นเพื่อช่วยให้คุณหลีกเลี่ยงข้อ จำกัด ที่น่ารังเกียจของขอบเขตที่ จำกัด :

#include <cstdio>
#include <cstdint>
#include <type_traits>

enum class Foo : uint16_t { A = 0, B = 1, C = 2 };

// ut_cast() casts the enum to its underlying type.
template <typename T>
inline auto ut_cast(T x) -> std::enable_if_t<std::is_enum_v<T>,std::underlying_type_t<T>>
{
    return static_cast<std::underlying_type_t<T> >(x);
}

int main(int argc, const char*argv[])
{
   Foo foo{static_cast<Foo>(ut_cast(Foo::B) | ut_cast(Foo::C))};
   Foo x{ Foo::C };
   if(0 != (ut_cast(x) & ut_cast(foo)) )
       puts("works!");
    else 
        puts("DID NOT WORK - ARGHH");
   return 0;
}

จริงอยู่ที่คุณต้องพิมพ์ut_cast()สิ่งต่าง ๆ ในแต่ละครั้ง แต่ในทางกลับกันโค้ดนี้ให้ผลลัพธ์ที่สามารถอ่านได้มากขึ้นในลักษณะเดียวกับที่ใช้static_cast<>()เมื่อเทียบกับการแปลงประเภทโดยนัยหรือoperator uint16_t()ชนิดของสิ่งต่าง ๆ

และขอให้ตรงไปตรงมาที่นี่การใช้ประเภทFooตามที่กล่าวไว้ข้างต้นมีอันตราย:

ที่อื่นบางคนอาจทำสลับตัวพิมพ์ใหญ่ - เล็กfooและไม่คาดหวังว่ามันจะมีค่ามากกว่าหนึ่งค่า ...

ดังนั้นการทิ้งรหัสไว้ด้วยut_cast()จะช่วยเตือนผู้อ่านว่ามีบางสิ่งที่น่าสนใจเกิดขึ้น

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