เหตุใดคลาส enum จึงเป็นที่ต้องการมากกว่า enum ธรรมดา


429

ผมได้ยินมาไม่กี่คนที่แนะนำให้ใช้ enum เรียนใน C ++ เพราะของความปลอดภัยประเภท

แต่นั่นหมายความว่าอย่างไร


57
เมื่อใครบางคนอ้างว่าการสร้างโปรแกรมบางอย่างเป็น "ความชั่วร้าย" พวกเขากำลังพยายามกีดกันคุณจากการคิดด้วยตัวเอง
Pete Becker

3
@NicolBolas: นี่เป็นคำถามที่เกี่ยวกับการตอบคำถามที่พบบ่อยกว่าเดิม (ไม่ว่าจะเป็นคำถามที่ถามบ่อยครั้งเป็นเรื่องที่แตกต่างออกไป)
David Rodríguez - dribeas

@ David มีการอภิปรายว่าเรื่องนี้ควรจะเป็นคำถามที่พบบ่อยหรือไม่เกิดขึ้นซึ่งเริ่มต้นที่นี่ ยินดีต้อนรับการป้อนข้อมูล
sbi

17
@PeteBecker บางครั้งพวกเขาแค่พยายามปกป้องคุณจากตัวคุณเอง
piccy

geeksforgeeks.org/...นี้ยังเป็นสถานที่ที่ดีที่จะเข้าใจVSenum enum class
mr_azad

คำตอบ:


472

C ++ มีสองประเภทenum:

  1. enum classES
  2. ธรรมดาenums

นี่คือตัวอย่างสองสามวิธีที่จะประกาศพวกเขา:

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 

ความแตกต่างระหว่างสองคืออะไร?

  • enum classes - ชื่อแจงนับเป็นท้องถิ่นเพื่อ enum และค่าของพวกเขาไม่ได้แปลงเป็นประเภทอื่น ๆ (เช่นอื่นenumหรือint)

  • ธรรมดาenums - โดยที่ชื่อตัวแจงนับอยู่ในขอบเขตเดียวกับ enum และค่าของมันจะแปลงเป็นจำนวนเต็มและชนิดอื่น ๆ โดยปริยาย

ตัวอย่าง:

enum Color { red, green, blue };                    // plain enum 
enum Card { red_card, green_card, yellow_card };    // another plain enum 
enum class Animal { dog, deer, cat, bird, human };  // enum class
enum class Mammal { kangaroo, deer, human };        // another enum class

void fun() {

    // examples of bad use of plain enums:
    Color color = Color::red;
    Card card = Card::green_card;

    int num = color;    // no problem

    if (color == Card::red_card) // no problem (bad)
        cout << "bad" << endl;

    if (card == Color::green)   // no problem (bad)
        cout << "bad" << endl;

    // examples of good use of enum classes (safe)
    Animal a = Animal::deer;
    Mammal m = Mammal::deer;

    int num2 = a;   // error
    if (m == a)         // error (good)
        cout << "bad" << endl;

    if (a == Mammal::deer) // error (good)
        cout << "bad" << endl;

}

สรุป:

enum classควรเลือกใช้ es เพราะจะทำให้เกิดความประหลาดใจน้อยลงซึ่งอาจนำไปสู่ข้อบกพร่อง


7
ตัวอย่างที่ดี ... มีวิธีรวมความปลอดภัยของประเภทคลาสกับการส่งเสริมเนมสเปซของรุ่น enum หรือไม่ นั่นคือถ้าฉันมีชั้นเรียนที่Aมีรัฐและฉันสร้างenum class State { online, offline };เป็นลูกของชั้นเรียนAฉันต้องการที่จะstate == onlineตรวจสอบภายในAแทนstate == State::online... เป็นไปได้ที่?
ทำเครื่องหมาย

31
Nope การโปรโมตเนมสเปซคือ Bad Thing ™และครึ่งหนึ่งของเหตุผลสำหรับenum classการกำจัดมัน
ลูกสุนัข

10
ใน C ++ 11 คุณสามารถใช้ enums ที่พิมพ์อย่างชัดเจนเช่น enum Animal: int ที่ไม่ได้ลงชื่อ {สุนัขกวางแมวนก}
Blasius Secundus

3
@Cat Plus Plus ฉันเข้าใจว่า @Oleksiy บอกว่ามันไม่ดี คำถามของฉันไม่ใช่ถ้า Oleksiy คิดว่ามันไม่ดี คำถามของฉันคือการขอรายละเอียดสิ่งที่ไม่ดีเกี่ยวกับเรื่องนี้ โดยเฉพาะเหตุผลที่ไม่ Oleksiy Color color = Color::redตัวอย่างเช่นพิจารณาเกี่ยวกับการที่ไม่ดี
chux - Reinstate Monica

9
@Cat Plus Plus ดังนั้นตัวอย่างที่ไม่ดีจะไม่เกิดขึ้นจนกว่าจะมีif (color == Card::red_card)บรรทัด 4 บรรทัดถัดจากความคิดเห็น (ซึ่งฉันเห็นตอนนี้ใช้กับครึ่งแรกของบล็อก) 2 บรรทัดของบล็อกเป็นตัวอย่างที่ไม่ดี 3 บรรทัดแรกไม่ใช่ปัญหา "บล็อกทั้งหมดเป็นสาเหตุที่ทำให้ enums ไม่ดี" ทำให้ฉันเพราะฉันคิดว่าคุณหมายถึงบางสิ่งผิดปกติกับสิ่งเหล่านั้นด้วย ฉันเห็นตอนนี้มันเป็นเพียงการตั้งค่า ไม่ว่าในกรณีใด ๆ ขอขอบคุณสำหรับความคิดเห็น
chux - Reinstate Monica

248

จากCj 11 คำถามที่พบบ่อยของ Bjarne Stroustrup :

enum classES ( "enums ใหม่", "enums แข็งแกร่ง") อยู่สามปัญหากับแบบดั้งเดิม c ++ enumerations:

  • enums ทั่วไปแปลงโดยปริยายเป็น int ทำให้เกิดข้อผิดพลาดเมื่อมีคนไม่ต้องการให้แจงนับเป็นจำนวนเต็ม
  • enums ทั่วไปส่งออก enumerators ของพวกเขาไปยังขอบเขตโดยรอบทำให้เกิดการปะทะกันของชื่อ
  • enumไม่สามารถระบุประเภทพื้นฐานของก่อให้เกิดความสับสนปัญหาความเข้ากันได้และทำให้การประกาศไปข้างหน้าเป็นไปไม่ได้

enums ใหม่คือ "enum class" เพราะพวกเขารวมแง่มุมของการแจกแจงแบบดั้งเดิม (ค่าชื่อ) กับแง่มุมของคลาส (สมาชิกที่มีขอบเขตและไม่มีการแปลง)

ดังนั้นตามที่ผู้ใช้รายอื่นกล่าวถึง "strong enums" จะทำให้รหัสปลอดภัยยิ่งขึ้น

ประเภทพื้นฐานของ "คลาสสิค" enumจะต้องเป็นประเภทจำนวนเต็มขนาดใหญ่พอที่จะพอดีกับค่าทั้งหมดของenum; intนี้เป็นปกติ นอกจากนี้แต่ละประเภทที่ระบุจะต้องเข้ากันได้กับcharหรือประเภทจำนวนเต็มลงนาม / ไม่ได้ลงนาม

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

ตัวอย่างเช่นฉันเคยเห็นโค้ดเช่นนี้มาหลายครั้ง:

enum E_MY_FAVOURITE_FRUITS
{
    E_APPLE      = 0x01,
    E_WATERMELON = 0x02,
    E_COCONUT    = 0x04,
    E_STRAWBERRY = 0x08,
    E_CHERRY     = 0x10,
    E_PINEAPPLE  = 0x20,
    E_BANANA     = 0x40,
    E_MANGO      = 0x80,
    E_MY_FAVOURITE_FRUITS_FORCE8 = 0xFF // 'Force' 8bits, how can you tell?
};

ในโค้ดข้างต้นผู้ไร้เดียงสาบางคนคิดว่าคอมไพเลอร์จะเก็บE_MY_FAVOURITE_FRUITSค่าลงในประเภท 8 บิตที่ไม่ได้ลงชื่อ ... แต่ไม่มีการรับประกันเกี่ยวกับมัน: คอมไพเลอร์อาจเลือกunsigned charหรือintหรือshortประเภทใด ๆ นั้นมีขนาดใหญ่พอที่จะใส่ enumค่าที่เห็นใน การเพิ่มสนามE_MY_FAVOURITE_FRUITS_FORCE8เป็นภาระและไม่ได้บังคับให้คอมไพเลอร์จะทำให้ชนิดของทางเลือกใด ๆ enumเกี่ยวกับชนิดพื้นฐานของ

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

และเพื่อให้เรื่องเลวถ้าเพื่อนร่วมงานบางส่วนเพิ่มลวกค่าใหม่ของเราenum:

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

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

เนื่องจาก C ++ 11 มีความเป็นไปได้ที่จะระบุชนิดที่จำเป็นสำหรับenumและenum class(ขอบคุณrdb ) ดังนั้นปัญหานี้จึงได้รับการแก้ไขอย่างเรียบร้อย:

enum class E_MY_FAVOURITE_FRUITS : unsigned char
{
    E_APPLE        = 0x01,
    E_WATERMELON   = 0x02,
    E_COCONUT      = 0x04,
    E_STRAWBERRY   = 0x08,
    E_CHERRY       = 0x10,
    E_PINEAPPLE    = 0x20,
    E_BANANA       = 0x40,
    E_MANGO        = 0x80,
    E_DEVIL_FRUIT  = 0x100, // Warning!: constant value truncated
};

การระบุชนิดข้อมูลพื้นฐานหากฟิลด์มีนิพจน์ที่อยู่นอกช่วงประเภทนี้คอมไพเลอร์จะบ่นแทนการเปลี่ยนประเภทข้อมูลอ้างอิง

ฉันคิดว่านี่เป็นการปรับปรุงด้านความปลอดภัยที่ดี

แล้วเหตุใดคลาสของ enum จึงเป็นที่ต้องการมากกว่า enum ธรรมดา ถ้าเราสามารถเลือกประเภทพื้นฐานสำหรับ scoped ( enum class) และ unscoped ( enum) enums อะไรที่ทำให้enum classตัวเลือกที่ดีกว่า:

  • intพวกเขาไม่ได้แปลงโดยปริยาย
  • พวกเขาไม่ทำให้เกิดมลภาวะเนมสเปซที่ล้อมรอบ
  • พวกเขาสามารถประกาศไปข้างหน้า

1
ฉันคิดว่าเราสามารถ จำกัด ประเภทฐาน enum สำหรับ enums ปกติได้เช่นกันตราบใดที่เรามี C ++ 11
Sagar Padhye

11
ขออภัยคำตอบนี้ผิด "enum class" ไม่เกี่ยวข้องกับความสามารถในการระบุประเภท นั่นเป็นคุณสมบัติอิสระที่มีอยู่ทั้งสำหรับ enums ปกติและสำหรับคลาส Enum
rdb

14
นี่คือข้อตกลง: * คลาส Enum เป็นคุณลักษณะใหม่ใน C ++ 11 * Enums ที่พิมพ์เป็นคุณสมบัติใหม่ใน C ++ 11 นี่คือคุณสมบัติใหม่ที่ไม่เกี่ยวข้องสองอย่างใน C ++ 11 คุณสามารถใช้ทั้งสองหรือคุณสามารถใช้หรือไม่
rdb

2
ผมคิดว่าอเล็กซ์ Allain ให้สมบูรณ์แบบที่สุดง่ายคำอธิบายที่ผมเคยเห็น แต่ในบล็อกนี้ที่ [ cprogramming.com/c++11/... Enumแบบดั้งเดิมนั้นดีสำหรับการใช้ชื่อแทนค่าจำนวนเต็มและหลีกเลี่ยงการใช้ preprocessor #defines ซึ่งเป็นสิ่งที่ดี - มันเพิ่มความชัดเจน คลาส enumลบแนวคิดของค่าตัวเลขของตัวแจงนับและแนะนำขอบเขตและการพิมพ์ที่แข็งแกร่งซึ่งจะเพิ่มขึ้น (ดีสามารถเพิ่มความถูกต้องของโปรแกรม :-) มันทำให้คุณเข้าใกล้การคิดเชิงวัตถุมากขึ้น
Jon Spencer

2
มันเป็นเรื่องที่น่าขบขันอยู่เสมอเมื่อคุณตรวจสอบโค้ดและทันใดนั้นOne Pieceก็เกิดขึ้น
Justin Time - Reinstate Monica

47

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

สำหรับเช่น:

enum class Color1 { red, green, blue };    //this will compile
enum class Color2 { red, green, blue };

enum Color1 { red, green, blue };    //this will not compile 
enum Color2 { red, green, blue };

สำหรับ enums พื้นฐานคอมไพเลอร์จะไม่สามารถแยกแยะว่าredมีการอ้างถึงชนิดColor1หรือColor2ในคำสั่ง hte ด้านล่าง

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)

1
@Oleksiy โอ้ฉันไม่ได้อ่านคำถามของคุณอย่างถูกต้อง การพิจารณาว่าเป็นสิ่งเสริมสำหรับผู้ที่ไม่รู้
Saksham

ไม่เป็นไร! ฉันเกือบลืมเรื่องนี้ไปแล้ว
Oleksiy

แน่นอนคุณต้องเขียนenum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }แก้ปัญหาเนมสเปซได้อย่างง่ายดาย อาร์กิวเมนต์เนมสเปซเป็นหนึ่งในสามข้อที่กล่าวถึงในที่นี้ซึ่งฉันไม่ได้ซื้อเลย
โจดังนั้น

2
@ โจดังนั้นทางออกนั้นเป็นวิธีแก้ปัญหาที่ไม่จำเป็น Enum: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }เทียบได้กับระดับ enum class Color1 { RED, GREEN, BLUE }Enum: การเข้าถึงคล้าย: COLOR1_REDvs Color1::REDแต่รุ่น Enum ต้องการให้คุณพิมพ์ "COLOR1" ในแต่ละค่าซึ่งให้พื้นที่เพิ่มเติมสำหรับการพิมพ์ผิดซึ่งลักษณะการทำงานของเนมสเปซของคลาส enum หลีกเลี่ยง
cdgraham

2
โดยใช้การวิจารณ์ที่สร้างสรรค์ เมื่อฉันพูดว่ามีที่ว่างมากขึ้นสำหรับการพิมพ์ผิดฉันหมายถึงเมื่อคุณกำหนดค่าเริ่มต้นenum Color1ซึ่งคอมไพเลอร์ไม่สามารถตรวจจับได้เนื่องจากมีแนวโน้มว่าจะยังคงเป็นชื่อ 'ถูกต้อง' ถ้าผมเขียนRED, GREENและอื่น ๆ โดยใช้ชั้น enum กว่ามันไม่สามารถแก้ไขไปenum Bananaเพราะมันต้องการให้คุณระบุColor1::REDเพื่อเข้าถึงค่า (อาร์กิวเมนต์ namespace) ที่ ยังคงมีเวลาที่ดีที่จะใช้enumแต่พฤติกรรม namespace ของenum classมักจะมีประโยชน์มาก
cdgraham

20

การแจกแจงใช้เพื่อแสดงชุดของค่าจำนวนเต็ม

classคำหลักหลังจากที่enumระบุว่านับเป็นพิมพ์มั่นและ enumerators มันถูกกำหนดขอบเขต วิธีนี้enumจะช่วยป้องกันการใช้ค่าคงที่ในชั้นเรียนโดยไม่ตั้งใจ

ตัวอย่างเช่น:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

ที่นี่เราไม่สามารถผสมค่าสัตว์และสัตว์เลี้ยงได้

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal

7

คำถามที่พบบ่อย C ++ 11ระบุไว้ด้านล่างคะแนน:

enums ทั่วไปแปลงโดยปริยายเป็น int ทำให้เกิดข้อผิดพลาดเมื่อมีคนไม่ต้องการให้แจงนับเป็นจำนวนเต็ม

enum color
{
    Red,
    Green,
    Yellow
};

enum class NewColor
{
    Red_1,
    Green_1,
    Yellow_1
};

int main()
{
    //! Implicit conversion is possible
    int i = Red;

    //! Need enum class name followed by access specifier. Ex: NewColor::Red_1
    int j = Red_1; // error C2065: 'Red_1': undeclared identifier

    //! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;
    int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'

    return 0;
}

enums ทั่วไปส่งออก enumerators ของพวกเขาไปยังขอบเขตโดยรอบทำให้เกิดการปะทะกันของชื่อ

// Header.h

enum vehicle
{
    Car,
    Bus,
    Bike,
    Autorickshow
};

enum FourWheeler
{
    Car,        // error C2365: 'Car': redefinition; previous definition was 'enumerator'
    SmallBus
};

enum class Editor
{
    vim,
    eclipes,
    VisualStudio
};

enum class CppEditor
{
    eclipes,       // No error of redefinitions
    VisualStudio,  // No error of redefinitions
    QtCreator
};

ไม่สามารถระบุชนิดพื้นฐานของ enum ทำให้เกิดความสับสนปัญหาความเข้ากันได้และทำให้การประกาศไปข้างหน้าเป็นไปไม่ได้

// Header1.h
#include <iostream>

using namespace std;

enum class Port : unsigned char; // Forward declare

class MyClass
{
public:
    void PrintPort(enum class Port p);
};

void MyClass::PrintPort(enum class Port p)
{
    cout << (int)p << endl;
}

.

// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
    PORT_1 = 0x01,
    PORT_2 = 0x02,
    PORT_3 = 0x04
};

.

// Source.cpp
#include "Header1.h"
#include "Header.h"

using namespace std;
int main()
{
    MyClass m;
    m.PrintPort(Port::PORT_1);

    return 0;
}

C ++ 11 ช่วยให้ "ไม่ใช่คลาส" enums ที่จะพิมพ์ได้เป็นอย่างดี ปัญหามลพิษเนมสเปซและอื่น ๆ ยังคงมีอยู่ ดูคำตอบที่เกี่ยวข้องที่มีอยู่เป็นเวลานานก่อนคำตอบนี้ ..
2864740

7
  1. ห้ามแปลงเป็น int โดยปริยาย
  2. สามารถเลือกประเภทที่รองรับ
  3. ENUM namespace เพื่อหลีกเลี่ยงการเกิดมลพิษ
  4. เมื่อเทียบกับระดับปกติสามารถประกาศไปข้างหน้า แต่ไม่มีวิธีการ

2

เป็นเรื่องที่น่าสังเกตยิ่งกว่าคำตอบอื่น ๆ เหล่านี้ที่ C ++ 20 แก้ปัญหาอย่างใดอย่างหนึ่งที่enum classมี: verbosity จินตนาการถึงสมมุติฐานenum class, Color.

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

นี้จะ verbose เมื่อเทียบกับธรรมดารูปแบบที่ชื่ออยู่ในขอบเขตทั่วโลกและดังนั้นจึงไม่จำเป็นต้องนำหน้าด้วยenumColor::

อย่างไรก็ตามใน C ++ 20 เราสามารถใช้using enumเพื่อแนะนำชื่อทั้งหมดใน enum กับขอบเขตปัจจุบันการแก้ปัญหา

void foo(Color c)
  using enum Color;
  switch (c) {
    case Red: ...;
    case Green: ...;
    case Blue: ...;
    // etc
  }
}

enum classดังนั้นตอนนี้มีเหตุผลที่จะไม่ใช้


1

เนื่องจากดังที่ได้กล่าวไว้ในคำตอบอื่น ๆ class enum ไม่สามารถแปลงเป็น int / bool ได้โดยปริยายดังนั้นจึงช่วยหลีกเลี่ยงโค้ด buggy เช่น:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

2
เพื่อให้ความคิดเห็นก่อนหน้าของฉันเสร็จสมบูรณ์โปรดทราบว่าตอนนี้ gcc มีคำเตือนที่เรียกว่า -Wint-in-bool-context ซึ่งจะตรวจจับข้อผิดพลาดชนิดนี้อย่างแน่นอน
Arnaud

0

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

class Test
{
public:
   // these call ProcessCommand() internally
   void TakeSnapshot();
   void RestoreSnapshot();
private:
   enum class Command // wouldn't be possible without 'class'
   {
        TakeSnapshot,
        RestoreSnapshot
   };
   void ProcessCommand(Command cmd); // signal the other thread or whatever
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.