ฉันควรใช้ #define, enum หรือ const?


125

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

  • สถิติใหม่
  • ลบบันทึก
  • แก้ไขบันทึก
  • บันทึกที่มีอยู่

ตอนนี้สำหรับแต่ละระเบียนฉันต้องการเก็บแอตทริบิวต์นี้ไว้ดังนั้นฉันจึงสามารถใช้ enum:

enum { xNew, xDeleted, xModified, xExisting }

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

showRecords(xNew | xDeleted);

ดูเหมือนว่าฉันจะมี appoaches ที่เป็นไปได้สามอย่าง:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

หรือ

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

หรือ

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

ข้อกำหนดของพื้นที่มีความสำคัญ (byte vs int) แต่ไม่สำคัญ ด้วยการกำหนดฉันสูญเสียความปลอดภัยประเภทและด้วยenumฉันสูญเสียพื้นที่บางส่วน (จำนวนเต็ม) และอาจต้องโยนเมื่อฉันต้องการดำเนินการแบบบิต ด้วยconstฉันคิดว่าฉันสูญเสียความปลอดภัยเนื่องจากการสุ่มuint8อาจเข้ามาโดยไม่ได้ตั้งใจ

มีวิธีอื่นที่สะอาดกว่านี้ไหม?

ถ้าไม่คุณจะใช้อะไรและทำไม?

ป.ล. โค้ดที่เหลือค่อนข้างสะอาด C ++ สมัยใหม่โดยไม่มี#defines และฉันใช้เนมสเปซและเทมเพลตในช่องว่างไม่กี่ช่องดังนั้นสิ่งเหล่านี้ก็ไม่เป็นปัญหาเช่นกัน


"ด้วย enum ฉันเสียช่องว่าง (จำนวนเต็ม)" ไม่จำเป็น. ดูstackoverflow.com/questions/366017/…และstackoverflow.com/questions/1113855/… (และ gcc's -fshort-enum (ฉันสมมติว่าคำตอบ C เหล่านี้ยังคงเป็นจริงใน C ++)
idbrii

@pydave ในกรณีที่คุณไม่แน่ใจเกี่ยวกับความเข้ากันได้ของ C และ C ++ ฉันพบว่าลิงค์นี้มีประโยชน์มากดูตัวอย่างเช่น enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
นี่เป็นหัวข้อเก่าที่มีคะแนนโหวตสูงมีเหตุผลที่จะไม่พูดถึงคลาส enum C ++ 11 สำหรับสถานการณ์ปัญหานี้หรือไม่
Brandin

โปรดทราบว่าให้enum RecordType : uint8_tรวมประเภทความปลอดภัยเข้าenumกับขนาดที่เล็กuint8_tแม้ว่าคุณจะยังคงต้องระบุตัวดำเนินการแบบบิต
Justin Time - คืนสถานะ Monica

คำตอบ:


88

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

วาง enum ในเนมสเปซเพื่อป้องกันไม่ให้ค่าคงที่สร้างมลพิษให้กับเนมสเปซส่วนกลาง

namespace RecordType {

enum ประกาศและกำหนดเวลาคอมไพล์ที่ตรวจสอบพิมพ์ ใช้การตรวจสอบประเภทเวลาคอมไพล์เสมอเพื่อให้แน่ใจว่าอาร์กิวเมนต์และตัวแปรได้รับประเภทที่ถูกต้อง ไม่จำเป็นต้องใช้ typedef ใน C ++

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

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

xInvalid = 16 };

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

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

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

เพิ่มusingคำสั่งหากคุณต้องการใช้ประเภทนี้บ่อยๆ

using RecordType ::TRecordType ;

ฟังก์ชันการตรวจสอบค่ามีประโยชน์ในการยืนยันเพื่อดักจับค่าที่ไม่ถูกต้องทันทีที่ใช้ ยิ่งคุณจับบั๊กได้เร็วเท่าไหร่ก็จะสามารถสร้างความเสียหายได้น้อยลงเท่านั้น

นี่คือตัวอย่างบางส่วนที่จะนำมารวมกัน

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

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

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

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


1
ส่วนใหญ่เป็นคำตอบที่ดี แต่คำถามระบุว่าสามารถรวมแฟล็กได้และฟังก์ชัน IsValidState () ไม่อนุญาตให้รวมกัน
Jonathan Leffler

3
@Jonathan Leffler: จากที่ฉันยืนฉันคิดว่า 'IsValidState' ไม่ควรทำอย่างนั้น 'IsValidMask' คือ
João Portela

1
ต้องการหรือIsValidMaskไม่ที่ไม่อนุญาตให้เลือกไม่มี (เช่น0)
Joachim Sauer

2
−1แนวคิดของการตรวจสอบประเภทรันไทม์เป็นสิ่งที่น่ารังเกียจ
ไชโยและ hth - Alf

54

ลืมคำจำกัดความ

พวกเขาจะสร้างมลพิษให้กับรหัสของคุณ

bitfields?

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

อย่าใช้สิ่งนั้น คุณกังวลเกี่ยวกับความเร็วมากกว่าการประหยัด 4 ints การใช้ฟิลด์บิตนั้นช้ากว่าการเข้าถึงประเภทอื่น ๆ

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

ที่มา: http://en.wikipedia.org/wiki/Bit_field :

และหากคุณต้องการเหตุผลเพิ่มเติมในการไม่ใช้ bitfields บางทีRaymond Chenอาจโน้มน้าวคุณในThe Old New Thing Post: การวิเคราะห์ผลประโยชน์ด้านต้นทุนของ bitfields สำหรับชุดบูลีนที่http://blogs.msdn.com/oldnewthing/ เก็บ / 2008/11/26 / 9143050.aspx

const int?

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

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

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

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

ปัญหาของแนวทางนี้คือรหัสของคุณรู้ค่าคงที่ของคุณซึ่งจะเพิ่มการมีเพศสัมพันธ์เล็กน้อย

enum

เช่นเดียวกับ const int ด้วยการพิมพ์ที่ค่อนข้างแรงกว่า

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

แม้ว่าพวกเขายังคงสร้างมลพิษให้กับเนมสเปซทั่วโลก โดยวิธีการ ... ลบ typedef คุณกำลังทำงานใน C ++ ประเภทของ enums และโครงสร้างเหล่านี้ก่อให้เกิดมลพิษต่อรหัสมากกว่าสิ่งอื่นใด

ผลที่ได้คือ:

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

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

อย่างที่คุณเห็น enum ของคุณสร้างมลพิษให้กับเนมสเปซทั่วโลก หากคุณใส่ enum นี้ในเนมสเปซคุณจะมีสิ่งต่อไปนี้:

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

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

int const ภายนอก?

หากคุณต้องการลดการมีเพศสัมพันธ์ (เช่นสามารถซ่อนค่าของค่าคงที่และแก้ไขได้ตามต้องการโดยไม่จำเป็นต้องมีการคอมไพล์ใหม่ทั้งหมด) คุณสามารถประกาศ ints เป็น extern ในส่วนหัวและเป็นค่าคงที่ในไฟล์ CPP ดังตัวอย่างต่อไปนี้:

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

และ:

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

คุณจะไม่สามารถใช้สวิตช์กับค่าคงที่เหล่านั้นได้ ในท้ายที่สุดเลือกพิษของคุณ ... :-p


5
ทำไมคุณถึงคิดว่า bitfields ช้า? คุณใช้รหัสโปรไฟล์จริงและใช้วิธีอื่นหรือไม่? แม้ว่าจะเป็นเช่นนั้นความชัดเจนอาจมีความสำคัญมากกว่าความเร็วทำให้ "ไม่เคยใช้สิ่งนั้น" ง่ายขึ้นเล็กน้อย
wnoise

"const คงที่ uint8 xNew;" ซ้ำซ้อนเท่านั้นเนื่องจากในตัวแปรเนมสเปซที่กำหนดขอบเขต C ++ เริ่มต้นเป็นการเชื่อมโยงภายใน ลบ "const" และมีการเชื่อมโยงภายนอก นอกจากนี้ "enum {... } RecordType;" ประกาศตัวแปรโกลบอลชื่อ "RecordType" ซึ่งประเภทคือ anonymous enum
bk1e

onebyone: ประการแรกสาเหตุหลักคือการที่ได้รับ (ไม่กี่ไบต์ถ้ามี) ถูกครอบงำโดยการสูญเสีย (เข้าถึงได้ช้าลงทั้งอ่านและเขียน) ...
paercebal

3
onebyone: ประการที่สองรหัสทั้งหมดที่ฉันผลิตในที่ทำงานหรือที่บ้านนั้นปลอดภัยโดยเนื้อแท้ ทำได้ง่ายมาก: ไม่มี globals ไม่มีสแตติกไม่แชร์ระหว่างเธรดเว้นแต่จะมีการป้องกันการล็อก การใช้สำนวนนี้จะทำลายความปลอดภัยของเธรดขั้นพื้นฐานนี้ และเพื่ออะไร? อาจจะไม่กี่ไบต์? ... :-) ...
paercebal

เพิ่มการอ้างอิงบทความของ Raymond Chen เกี่ยวกับต้นทุนแฝงของ bitfields
paercebal

30

คุณตัด std :: bitset แล้วหรือยัง? ชุดของธงเป็นสิ่งที่มีไว้สำหรับ ทำ

typedef std::bitset<4> RecordType;

แล้วก็

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

เนื่องจากมีตัวดำเนินการจำนวนมากเกินพิกัดสำหรับบิตเซ็ตตอนนี้คุณสามารถทำได้

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

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

สมมติว่าคุณมีการปกครองออก bitset ผมลงคะแนนเสียงสำหรับการenum

ฉันไม่ซื้อว่าการหล่อ enums เป็นข้อเสียร้ายแรง - โอเคดังนั้นมันจึงมีเสียงดังเล็กน้อยและการกำหนดค่านอกระยะให้กับ enum นั้นเป็นพฤติกรรมที่ไม่ได้กำหนดดังนั้นในทางทฤษฎีจึงเป็นไปได้ที่จะยิงตัวเองด้วย C ++ ที่ผิดปกติ การใช้งาน แต่ถ้าคุณทำเมื่อจำเป็นเท่านั้น (ซึ่งก็คือเมื่อเปลี่ยนจาก int ไปเป็น enum iirc) มันเป็นรหัสปกติที่ผู้คนเคยเห็นมาก่อน

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

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

สำหรับความชอบฉันจะใส่ "= 2" ไว้ใน enum ด้วย ไม่จำเป็น แต่ "หลักการของความประหลาดใจอย่างน้อยที่สุด" ชี้ให้เห็นว่าคำจำกัดความทั้ง 4 คำควรมีลักษณะเหมือนกัน


1
อันที่จริงฉันไม่ได้พิจารณาบิตเซ็ตเลย อย่างไรก็ตามฉันไม่แน่ใจว่ามันจะดี ด้วยบิตเซ็ตฉันต้องกำหนดบิตเป็น 1, 2, 3, 4 ซึ่งจะทำให้โค้ดอ่านได้น้อยลง - หมายความว่าฉันอาจใช้ enum เพื่อ 'ตั้งชื่อ' บิต อาจช่วยประหยัดพื้นที่ได้ ขอบคุณ
Milan Babuškov

มิลานคุณไม่จำเป็นต้อง "ตั้งชื่อ" บิตโดยใช้ enum คุณสามารถใช้บิตที่กำหนดไว้ล่วงหน้าตามที่แสดงด้านบน หากคุณต้องการเปิดบิตหนึ่งแทนที่จะเป็น my_bitset.flip (1) คุณจะต้องทำ my_bitset | = xNew;
moswald

สิ่งนี้มุ่งตรงมาที่คุณน้อยลงและมากขึ้นที่ STL แต่: ฉันต้องถามจริงๆ: ทำไมคุณถึงใช้bitsetสิ่งนี้? โดยปกติจะแปลเป็น a long(ใน iirc การใช้งานของฉันใช่สิ้นเปลืองเพียงใด) หรือประเภทอินทิกรัลที่คล้ายกันสำหรับแต่ละองค์ประกอบดังนั้นทำไมไม่ใช้อินทิกรัลที่ไม่คลุมเครือล่ะ (หรือปัจจุบันconstexprไม่มีพื้นที่เก็บข้อมูล)
underscore_d

[แก้ไขหมดเวลา] ... แต่แล้วฉันก็ไม่เคยเข้าใจเหตุผลของbitsetชั้นเรียนเลยจริงๆนอกจากสิ่งที่ดูเหมือนจะเป็นกระแสซ้ำซากในการอภิปรายเกี่ยวกับ 'เอ่อเราต้องปกปิดรากศัพท์ระดับต่ำที่ไม่น่ารังเกียจของภาษา '
underscore_d

" uint8ตัวแปรและพารามิเตอร์คงไม่ใช้สแต็กน้อยกว่าints" ผิด หากคุณมีซีพียูที่มีรีจิสเตอร์ 8 บิตintจำเป็นต้องมีอย่างน้อย 2 รีจิสเตอร์ในขณะที่uint8_tต้องการเพียง 1 ดังนั้นคุณจะต้องมีพื้นที่สแต็กเพิ่มขึ้นเนื่องจากคุณมีแนวโน้มที่จะไม่อยู่ในรีจิสเตอร์ (ซึ่งช้ากว่าและสามารถเพิ่มขนาดโค้ดได้ ( ขึ้นอยู่กับชุดคำสั่ง)). (คุณมีประเภทที่มันควรจะเป็นuint8_tไม่ได้uint8)
12431234123412341234123

8

นี่คือบทความสองสามข้อเกี่ยวกับ const กับ macros vs. enums:

Symbolic Constants
Enumeration Constants เทียบกับ Constant Objects

ฉันคิดว่าคุณควรหลีกเลี่ยงมาโครโดยเฉพาะเนื่องจากคุณเขียนโค้ดใหม่ส่วนใหญ่อยู่ใน C ++ ที่ทันสมัย


5

ถ้าเป็นไปได้อย่าใช้มาโคร พวกเขาไม่ได้รับการชื่นชมมากเกินไปเมื่อพูดถึง C ++ สมัยใหม่


4
จริง สิ่งที่ฉันเกลียดเกี่ยวกับมาโครคือคุณไม่สามารถก้าวเข้าไปหามันได้ถ้ามันผิด
Carl

ฉันคิดว่านั่นเป็นสิ่งที่สามารถแก้ไขได้ในคอมไพเลอร์
celticminstrel

4

Enums จะเหมาะสมกว่าเนื่องจากให้ "ความหมายต่อตัวระบุ" รวมทั้งความปลอดภัยของประเภท คุณสามารถบอกได้อย่างชัดเจนว่า "xDeleted" เป็น "RecordType" และนั่นแสดงถึง "ประเภทของระเบียน" (ว้าว!) แม้จะผ่านไปหลายปี Consts ต้องการความคิดเห็นสำหรับสิ่งนั้นและพวกเขาจะต้องมีการขึ้นและลงในรหัส


4

ด้วยการกำหนดฉันสูญเสียความปลอดภัยประเภท

ไม่จำเป็น...

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

และด้วย enum ฉันสูญเสียพื้นที่บางส่วน (จำนวนเต็ม)

ไม่จำเป็น - แต่คุณต้องชัดเจนที่จุดจัดเก็บ ...

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

และอาจต้องแคสต์เมื่อฉันต้องการดำเนินการแบบบิต

คุณสามารถสร้างตัวดำเนินการเพื่อขจัดความเจ็บปวดจากสิ่งนั้น:

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

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

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

มีวิธีอื่นที่สะอาดกว่านี้ไหม? / ถ้าไม่คุณจะใช้อะไรและทำไม?

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

คุณสามารถโต้แย้งได้ว่านี่ "สะอาดกว่า":

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

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

ฉันไม่แยแส: ชุดบิตข้อมูลแน่นขึ้น แต่โค้ดเติบโตขึ้นอย่างมาก ... ขึ้นอยู่กับจำนวนวัตถุที่คุณมีและ lamdbas ที่สวยงามเหมือนที่เป็นอยู่นั้นยังคงยุ่งเหยิงและยากที่จะทำให้ถูกต้องกว่า OR ในระดับบิต

BTW / - ข้อโต้แย้งเกี่ยวกับ IMHO ที่ค่อนข้างอ่อนแอของความปลอดภัยของเธรด - จำได้ดีที่สุดว่าเป็นการพิจารณาเบื้องหลังแทนที่จะเป็นแรงผลักดันการตัดสินใจที่โดดเด่น การแบ่งปัน mutex ใน bitfields เป็นวิธีปฏิบัติที่มีแนวโน้มมากขึ้นแม้ว่าจะไม่ทราบถึงการบรรจุของพวกเขาก็ตาม (mutexes เป็นสมาชิกข้อมูลที่ค่อนข้างใหญ่ - ฉันต้องกังวลเกี่ยวกับประสิทธิภาพการทำงานเพื่อพิจารณาให้มี mutex หลายตัวในสมาชิกของวัตถุเดียวและฉันจะพิจารณาอย่างรอบคอบ พอที่จะสังเกตเห็นว่ามันเป็นบิตฟิลด์) ประเภทย่อยขนาดคำใด ๆ อาจมีปัญหาเดียวกัน (เช่นกuint8_t) อย่างไรก็ตามคุณสามารถลองใช้รูปแบบการเปรียบเทียบและแลกเปลี่ยนอะตอมได้หากคุณหมดหวังที่จะเกิดภาวะพร้อมกันที่สูงขึ้น


1
+1 ดี แต่operator|ควรจะโยนไปชนิดจำนวนเต็ม ( unsigned int) |ก่อนที่การเรียนการสอน มิฉะนั้นoperator|จะเรียกตัวเองซ้ำและทำให้เกิดสแต็กรันไทม์ ฉันขอแนะนำ: return RecordType( unsigned(lhs) | unsigned(rhs) );. ไชโย
olibre

3

แม้ว่าคุณจะต้องใช้ 4 ไบต์ในการจัดเก็บ enum (ฉันไม่ค่อยคุ้นเคยกับ C ++ - ฉันรู้ว่าคุณสามารถระบุประเภทพื้นฐานใน C # ได้) ก็ยังคุ้มค่า - ใช้ enums

ในวันนี้และอายุของเซิร์ฟเวอร์ที่มีหน่วยความจำ GB สิ่งต่างๆเช่น 4 ไบต์กับหน่วยความจำ 1 ไบต์ในระดับแอปพลิเคชันโดยทั่วไปไม่สำคัญ แน่นอนว่าหากในสถานการณ์เฉพาะของคุณการใช้หน่วยความจำนั้นสำคัญ (และคุณไม่สามารถรับ C ++ เพื่อใช้ไบต์เพื่อสำรอง enum ได้) คุณสามารถพิจารณาเส้นทาง 'คงที่ const' ได้

ในตอนท้ายของวันคุณต้องถามตัวเองว่าคุ้มค่ากับการบำรุงรักษาโดยใช้ 'static const' เพื่อประหยัดหน่วยความจำ 3 ไบต์สำหรับโครงสร้างข้อมูลของคุณหรือไม่?

สิ่งอื่นที่ควรทราบ - IIRC บน x86 โครงสร้างข้อมูลมีการจัดแนว 4 ไบต์ดังนั้นหากคุณไม่มีองค์ประกอบความกว้างไบต์จำนวนหนึ่งในโครงสร้าง "บันทึก" ของคุณก็อาจไม่สำคัญ ทดสอบและตรวจสอบให้แน่ใจก่อนที่คุณจะทำการแลกเปลี่ยนในด้านการบำรุงรักษาสำหรับประสิทธิภาพ / พื้นที่


คุณสามารถระบุประเภทพื้นฐานใน C ++ ได้ตามการแก้ไขภาษา C ++ 11 จนถึงตอนนั้นฉันเชื่อว่า "อย่างน้อยก็ใหญ่พอที่จะจัดเก็บและใช้เป็นช่องบิตสำหรับตัวนับที่ระบุทั้งหมด แต่อาจintจะเล็กเกินไป" [หากคุณไม่ได้ระบุประเภทพื้นฐานใน C ++ 11 จะใช้พฤติกรรมเดิม ในทางกลับกันenum classประเภทพื้นฐานของC ++ 11 จะมีค่าเริ่มต้นอย่างชัดเจนintหากไม่ได้ระบุไว้เป็นอย่างอื่น]
เวลาจัสติน - คืนสถานะโมนิกา

3

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

ระวังแม้ว่า ท้ายที่สุดแพ็คเกจนี้ใช้เทมเพลตและมาโคร!


ดูเหมือน overkill สำหรับแอปเล็ก ๆ ของฉัน แต่ดูเหมือนเป็นทางออกที่ดี
Milan Babuškov

2

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

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

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


บางครั้งโดยรวมบางครั้งเป็นธง และฉันต้องทดสอบด้วยว่ามีการตั้งค่าสถานะที่แน่นอนหรือไม่ (เมื่อฉันผ่านมันโดยรวม)
Milan Babuškov

เมื่อแยกกันก็ขอ int เมื่ออยู่ด้วยกันให้ส่งโครงสร้าง
wnoise

มันจะไม่ดีขึ้น การเข้าถึงช่องบิตช้ากว่าสิ่งอื่นใด
paercebal

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

เรียกใช้การทดสอบการอ่านอย่างง่ายฉันได้รับ 5.50-5.58 วินาทีสำหรับการมาสก์บิตเทียบกับ 5.45-5.59 สำหรับการเข้าถึงบิตฟิลด์ ค่อนข้างแยกไม่ออก
wnoise

2

ฉันอาจจะไม่ใช้ enum สำหรับสิ่งประเภทนี้ที่สามารถรวมค่าเข้าด้วยกันได้โดยทั่วไปแล้ว enums จะเป็นสถานะพิเศษซึ่งกันและกัน

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

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

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


1
มีแบบอย่างเพียงพอสำหรับสิ่งนั้นโดยเฉพาะอย่างยิ่งในค่าคงที่สำหรับ ioctl () ฉันชอบใช้ค่าคงที่ฐานสิบหกมากกว่า: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler

2

ขึ้นอยู่กับKISS , การทำงานร่วมกันในระดับสูงและการมีเพศสัมพันธ์ต่ำถามคำถามเหล่านี้ -

  • ใครต้องรู้? ชั้นเรียนห้องสมุดของฉันชั้นเรียนอื่น ๆ ห้องสมุดอื่นบุคคลที่สาม
  • ฉันต้องให้สิ่งที่เป็นนามธรรมระดับใด ผู้บริโภคเข้าใจการทำงานของบิตหรือไม่
  • ฉันจะต้องเชื่อมต่อกับ VB / C # ฯลฯ หรือไม่?

มีหนังสือที่ยอดเยี่ยม " การออกแบบซอฟต์แวร์ C ++ ขนาดใหญ่ " ซึ่งส่งเสริมประเภทพื้นฐานภายนอกหากคุณสามารถหลีกเลี่ยงการพึ่งพาไฟล์ส่วนหัว / อินเทอร์เฟซอื่นที่คุณควรพยายามทำ


1
ก) 5-6 ชั้นเรียน b) มีเพียงฉันเท่านั้นเป็นโครงการชายคนเดียว c) ไม่มีการเชื่อมต่อ
Milan Babuškov

2

หากคุณกำลังใช้ Qt คุณควรจะมีรูปลักษณ์สำหรับQFlags คลาส QFlags เป็นวิธีการจัดเก็บหรือการรวมกันของค่า enum ที่ปลอดภัย


ไม่ไม่มี Qt. จริงๆแล้วมันคือโครงการ wxWidgets
Milan Babuškov

0

ฉันอยากไปด้วย

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

เพียงเพราะ:

  1. มันสะอาดกว่าและทำให้รหัสอ่านและบำรุงรักษาได้
  2. มันจัดกลุ่มค่าคงที่ตามเหตุผล
  3. เวลาของโปรแกรมเมอร์สำคัญกว่าเว้นแต่งานของคุณคือการบันทึก 3 ไบต์เหล่านั้น

ฉันสามารถมีบันทึกคลาสได้เป็นล้านอินสแตนซ์ได้อย่างง่ายดายดังนั้นจึงอาจมีความสำคัญ OTOH นั่นคือความแตกต่างระหว่าง 1MB และ 4MB ดังนั้นฉันไม่ควรกังวล
Milan Babuškov

@Vivek: คุณพิจารณาข้อจำกัดความกว้างจำนวนเต็มแล้วหรือยัง? โดยเฉพาะอย่างยิ่งก่อน C ++ 11
user2672165

0

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

เป็นโมฆะ setDeleted ();

เป็นโมฆะ clearDeleted ();

บูล isDeleted ();

ฯลฯ ... (หรืออะไรก็ตามที่เหมาะสมกับการประชุม)

สามารถตรวจสอบชุดค่าผสมได้ (ในกรณีที่ชุดค่าผสมทั้งหมดไม่ถูกต้องตามกฎหมายเช่นถ้าไม่สามารถตั้งค่า "ใหม่" และ "ลบ" พร้อมกันได้) หากคุณเพิ่งใช้มาสก์บิต ฯลฯ โค้ดที่กำหนดสถานะจะต้องตรวจสอบความถูกต้องคลาสก็สามารถห่อหุ้มตรรกะนั้นได้เช่นกัน

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

สำหรับทุกสิ่งนั้นหากคุณกังวลเกี่ยวกับพื้นที่จัดเก็บข้อมูลคุณยังคงสามารถให้คลาสมีสมาชิกข้อมูล 'char' ได้เท่านั้นดังนั้นควรใช้พื้นที่เก็บข้อมูลเพียงเล็กน้อย (สมมติว่าไม่ใช่เสมือน) แน่นอนขึ้นอยู่กับฮาร์ดแวร์ ฯลฯ คุณอาจมีปัญหาในการจัดตำแหน่ง

คุณอาจมีค่าบิตที่แท้จริงซึ่งมองไม่เห็นในส่วนที่เหลือของ 'โลก' หากอยู่ในเนมสเปซที่ไม่ระบุชื่อภายในไฟล์ cpp แทนที่จะอยู่ในไฟล์ส่วนหัว

หากคุณพบว่ารหัสที่ใช้ enum / # define / bitmask etc มีรหัส 'support' จำนวนมากเพื่อจัดการกับชุดค่าผสมที่ไม่ถูกต้องการบันทึกและอื่น ๆ การห่อหุ้มในชั้นเรียนอาจคุ้มค่าที่จะพิจารณา แน่นอนว่าปัญหาง่าย ๆ ส่วนใหญ่จะดีกว่าด้วยวิธีแก้ปัญหาง่ายๆ ...


น่าเสียดายที่การประกาศจะต้องอยู่ในไฟล์. h เนื่องจากถูกใช้ข้ามโปรเจ็กต์ (ใช้โดยคลาส 5-6 คลาส)
Milan Babuškov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.