เนมสเปซสำหรับประเภท enum - แนวทางปฏิบัติที่ดีที่สุด


102

บ่อยครั้งหนึ่งต้องมีการแจกแจงหลายประเภทด้วยกัน บางครั้งคนหนึ่งมีชื่อปะทะกัน วิธีแก้ปัญหาสองวิธีสำหรับสิ่งนี้: ใช้เนมสเปซหรือใช้ชื่อองค์ประกอบ enum ที่ 'ใหญ่กว่า' อย่างไรก็ตามโซลูชันเนมสเปซยังมีการใช้งานที่เป็นไปได้สองแบบ: คลาสดัมมี่ที่มี enum ซ้อนกันหรือเนมสเปซแบบเต็ม

ฉันกำลังมองหาข้อดีข้อเสียของทั้งสามวิธี

ตัวอย่าง:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

19
ก่อนอื่นฉันจะใช้ Color :: Red, Feeling: Angry, etc
abatishchev

คำถามที่ดีฉันใช้วิธีเนมสเปซ .... ;)
MiniScalope

18
คำนำหน้า "c" ในทุกสิ่งทำให้อ่านไม่ได้
ผู้ใช้

8
@ ผู้ใช้: ทำไมขอบคุณสำหรับการเปิดการสนทนาภาษาฮังการี :)
xtofl

5
โปรดทราบว่าคุณไม่จำเป็นต้องตั้งชื่อ enum เหมือนในenum e {...}enums สามารถเป็นแบบไม่ระบุตัวตนได้กล่าวคือenum {...}เหมาะสมกว่าเมื่อรวมอยู่ในเนมสเปซหรือคลาส
kralyk

คำตอบ:


73

คำตอบ C ++ 03 ดั้งเดิม:

ประโยชน์จากnamespace(กว่าclass) คือการที่คุณสามารถใช้usingการประกาศเมื่อคุณต้องการ

ปัญหากับการใช้namespaceเป็นที่ namespaces สามารถขยายตัวอื่น ๆ ในรหัส ในโปรเจ็กต์ขนาดใหญ่คุณจะไม่ได้รับการรับรองว่าสอง enums ที่แตกต่างกันจะไม่คิดว่าทั้งคู่ถูกเรียกeFeelings

สำหรับโค้ดที่ดูง่ายขึ้นฉันใช้ a structเนื่องจากคุณน่าจะต้องการให้เนื้อหาเป็นแบบสาธารณะ

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

ใหม่กว่าคำแนะนำ C ++ 11:

หากคุณใช้ C ++ 11 หรือใหม่กว่าenum classจะกำหนดขอบเขตค่า enum ภายในชื่อ enum โดยปริยาย

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


4
ฉันเห็นด้วยกับโครงสร้างความคิด และขอบคุณสำหรับคำชม :)
xtofl

3
+1 ฉันจำไวยากรณ์ C ++ 11 "enum class" ไม่ได้ หากไม่มีคุณสมบัตินั้น enums จะไม่สมบูรณ์
Grault

เป็นตัวเลือกในการใช้ "ใช้" สำหรับขอบเขตโดยนัยของ "enum class" เช่นจะเพิ่ม 'โดยใช้สี :: e;' เพื่อให้โค้ดอนุญาตให้ใช้ 'cRed' และรู้ว่าควรเป็น Color :: e :: cRed?
JVApen


11

ฉันได้ผสมคำตอบก่อนหน้านี้ไว้กับสิ่งนี้: (แก้ไข: สิ่งนี้มีประโยชน์สำหรับ C ++ 11 ก่อนหน้านี้เท่านั้นหากคุณใช้ C ++ 11 ให้ใช้enum class)

ฉันมีไฟล์ส่วนหัวขนาดใหญ่หนึ่งไฟล์ที่มี enums โปรเจ็กต์ทั้งหมดของฉันเนื่องจาก enums เหล่านี้ใช้ร่วมกันระหว่างคลาสของผู้ปฏิบัติงานและมันไม่สมเหตุสมผลที่จะใส่ enums ในคลาสของผู้ปฏิบัติงานเอง

structหลีกเลี่ยงส่วนกลาง: น้ำตาลประโยคและtypedefช่วยให้คุณจริงประกาศตัวแปรของ enums เหล่านี้ภายในชั้นเรียนของผู้ปฏิบัติงานอื่น ๆ

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

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}

9

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

จากตัวอย่างของคุณด้านบน:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

คุณช่วยให้คำแนะนำได้ไหมว่าทำไมคุณถึงชอบใช้เนมสเปซมากกว่าคลาส
xtofl

@xtofl: คุณไม่สามารถเขียน 'โดยใช้สีคลาส' ได้
MSalters

2
@MSalters: คุณไม่สามารถเขียนได้Colors someColor = Red;เนื่องจากเนมสเปซไม่ได้เป็นประเภท คุณจะต้องเขียนColors::e someColor = Red;แทนซึ่งค่อนข้างสวนทางกับธรรมชาติ
SasQ

@SasQ คุณไม่จำเป็นต้องใช้Colors::e someColorแม้กระทั่งกับ a struct/classถ้าคุณต้องการใช้ในswitchคำสั่ง? หากคุณใช้แบบไม่ระบุตัวตนenumสวิตช์จะไม่สามารถประเมินไฟล์struct.
ปริศนาของ Macbeth

1
ขออภัยconst e cฉันอ่านแทบไม่ออก :-) อย่าทำอย่างนั้น อย่างไรก็ตามการใช้เนมสเปซก็ใช้ได้
dhaumann

7

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

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

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

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


การไม่เปิดชั้นเรียนซ้ำก็เป็นผลเสียที่อาจเกิดขึ้นได้เช่นกัน รายการสีไม่ จำกัด เช่นกัน
MSalters

1
ฉันคิดว่าการไม่ทำซ้ำชั้นเรียนเป็นข้อดี ถ้าฉันต้องการสีมากขึ้นฉันจะคอมไพล์คลาสใหม่ด้วยสีเพิ่มเติม หากฉันไม่สามารถทำสิ่งนี้ได้ (บอกว่าฉันไม่มีรหัส) ฉันก็ไม่ต้องการแตะต้องมันไม่ว่าในกรณีใด ๆ
Thomas Eding

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

ดังนั้นเมื่อคุณต้องการขยาย enum คุณควรทำให้เป็นประเภทใหม่ที่ได้มาจากแบบแรก (ถ้าเป็นไปได้ง่ายใน C ++ ... ; /) จากนั้นโค้ดใหม่สามารถใช้งานได้อย่างปลอดภัยซึ่งเข้าใจค่าใหม่เหล่านั้น แต่จะยอมรับเฉพาะค่าเก่าเท่านั้น (โดยการแปลงค่าลง) โดยโค้ดเก่า พวกเขาไม่ควรยอมรับคนใดคนหนึ่งจากค่าใหม่เหล่านั้นว่าเป็นประเภทที่ไม่ถูกต้อง (ค่าเพิ่มเติม) เฉพาะค่าเก่าที่พวกเขาเข้าใจเท่านั้นที่ยอมรับว่าเป็นประเภท (ฐาน) ที่ถูกต้อง (และบังเอิญเป็นประเภทใหม่ด้วยดังนั้นจึงสามารถยอมรับรหัสใหม่ได้เช่นกัน)
SasQ

7

ข้อดีของการใช้คลาสคือคุณสามารถสร้างคลาสที่มีคุณสมบัติครบถ้วนด้านบนได้

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

ดังตัวอย่างข้างต้นแสดงโดยใช้คลาสคุณสามารถ:

  1. ห้าม (น่าเศร้าไม่ใช่เวลาคอมไพล์) C ++ ไม่อนุญาตให้แคสต์จากค่าที่ไม่ถูกต้อง
  2. ตั้งค่าเริ่มต้น (ไม่ใช่ศูนย์) สำหรับ enums ที่สร้างขึ้นใหม่
  3. เพิ่มวิธีการเพิ่มเติมเช่นการส่งคืนการแสดงสตริงของตัวเลือก

โปรดทราบว่าคุณต้องประกาศoperator enum_type()เพื่อที่ C ++ จะได้รู้วิธีแปลงคลาสของคุณเป็น enum พื้นฐาน มิฉะนั้นคุณจะไม่สามารถส่งผ่านประเภทไปยังswitchคำสั่งได้


วิธีแก้ปัญหานี้เกี่ยวข้องกับสิ่งที่แสดงอยู่ที่นี่หรือไม่: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enumฉันกำลังคิดว่าจะทำอย่างไรให้เป็นเทมเพลตโดยที่ฉันไม่ต้องเขียนรูปแบบนี้ใหม่ทุกครั้ง ฉันจำเป็นต้องใช้มัน
SasQ

@SasQ: ดูเหมือนว่าใช่ นั่นอาจเป็นความคิดเดียวกัน อย่างไรก็ตามฉันไม่แน่ใจว่าเทมเพลตจะมีประโยชน์หรือไม่เว้นแต่คุณจะเพิ่มวิธีการ 'ทั่วไป' จำนวนมากในนั้น
MichałGórny

1. ไม่จริง. คุณสามารถตรวจสอบเวลาคอมไพล์ว่า enum ถูกต้องผ่าน const_expr หรือผ่านตัวสร้างส่วนตัวสำหรับ int
xryl669

5

เนื่องจาก enums ถูกกำหนดขอบเขตไว้ในขอบเขตการปิดล้อมจึงควรห่อหุ้มไว้ในบางสิ่งเพื่อหลีกเลี่ยงการสร้างมลพิษให้กับเนมสเปซทั่วโลกและเพื่อช่วยหลีกเลี่ยงการชนกันของชื่อ ฉันชอบเนมสเปซในการเรียนเพียงเพราะให้namespaceความรู้สึกเหมือนกระเป๋าถือในขณะที่ให้classความรู้สึกเหมือนเป็นวัตถุที่แข็งแกร่ง ( structเปรียบเทียบกับclassการอภิปราย) ประโยชน์ที่เป็นไปได้ของเนมสเปซคือสามารถขยายได้ในภายหลัง - มีประโยชน์หากคุณกำลังจัดการกับโค้ดของบุคคลที่สามที่คุณไม่สามารถแก้ไขได้

นี่คือสิ่งที่น่าสงสัยทั้งหมดเมื่อเราได้เรียน enum ด้วย C ++ 0x


ชั้นเรียน enum ... ต้องดู!
xtofl

3

ฉันมักจะห่อ enums ของฉันในชั้นเรียน

ตามสัญญาณของ Richard Corden ประโยชน์ของคลาสคือเป็นประเภทหนึ่งในความหมาย c ++ คุณจึงสามารถใช้กับเทมเพลตได้

ฉันมีกล่องเครื่องมือพิเศษ :: คลาส Enum สำหรับความต้องการของฉันซึ่งฉันเชี่ยวชาญสำหรับทุกเทมเพลตที่มีฟังก์ชันพื้นฐาน (ส่วนใหญ่: การแมปค่า enum กับสตริง std :: เพื่อให้ I / O อ่านง่ายขึ้น)

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

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

มันรบกวนฉันเสมอที่คอมไพเลอร์จะไม่จับสิ่งนี้เนื่องจากคุณเหลือค่า enum ที่ไม่มีความรู้สึก (และคุณจะไม่คาดคิด)

ในทำนองเดียวกัน:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

หลักอีกครั้งจะส่งคืนข้อผิดพลาด

ปัญหาคือคอมไพเลอร์จะพอดีกับ enum ในการแทนค่าที่เล็กที่สุดที่มีอยู่ (ในที่นี้เราต้องการ 2 บิต) และทุกอย่างที่พอดีกับการแทนค่านี้ถือว่าเป็นค่าที่ถูกต้อง

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

โดยรวมแล้วผู้ช่วยตัวน้อยของฉันช่วยบรรเทาสิ่งต่าง ๆ ให้กับ enums ของฉันได้อย่างแท้จริง (แน่นอนว่ามันเพิ่มค่าใช้จ่ายบางส่วน) และเป็นไปได้เพียงเพราะฉันซ้อนแต่ละ enum ในโครงสร้างของมันเอง :)


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