ผมได้ยินมาไม่กี่คนที่แนะนำให้ใช้ enum เรียนใน C ++ เพราะของความปลอดภัยประเภท
แต่นั่นหมายความว่าอย่างไร
ผมได้ยินมาไม่กี่คนที่แนะนำให้ใช้ enum เรียนใน C ++ เพราะของความปลอดภัยประเภท
แต่นั่นหมายความว่าอย่างไร
คำตอบ:
C ++ มีสองประเภทenum
:
enum class
ESenum
sนี่คือตัวอย่างสองสามวิธีที่จะประกาศพวกเขา:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
ความแตกต่างระหว่างสองคืออะไร?
enum class
es - ชื่อแจงนับเป็นท้องถิ่นเพื่อ enum และค่าของพวกเขาไม่ได้แปลงเป็นประเภทอื่น ๆ (เช่นอื่นenum
หรือint
)
ธรรมดาenum
s - โดยที่ชื่อตัวแจงนับอยู่ในขอบเขตเดียวกับ 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 เพราะจะทำให้เกิดความประหลาดใจน้อยลงซึ่งอาจนำไปสู่ข้อบกพร่อง
A
มีรัฐและฉันสร้างenum class State { online, offline };
เป็นลูกของชั้นเรียนA
ฉันต้องการที่จะstate == online
ตรวจสอบภายในA
แทนstate == State::online
... เป็นไปได้ที่?
enum class
การกำจัดมัน
Color color = Color::red
ตัวอย่างเช่นพิจารณาเกี่ยวกับการที่ไม่ดี
if (color == Card::red_card)
บรรทัด 4 บรรทัดถัดจากความคิดเห็น (ซึ่งฉันเห็นตอนนี้ใช้กับครึ่งแรกของบล็อก) 2 บรรทัดของบล็อกเป็นตัวอย่างที่ไม่ดี 3 บรรทัดแรกไม่ใช่ปัญหา "บล็อกทั้งหมดเป็นสาเหตุที่ทำให้ enums ไม่ดี" ทำให้ฉันเพราะฉันคิดว่าคุณหมายถึงบางสิ่งผิดปกติกับสิ่งเหล่านั้นด้วย ฉันเห็นตอนนี้มันเป็นเพียงการตั้งค่า ไม่ว่าในกรณีใด ๆ ขอขอบคุณสำหรับความคิดเห็น
จากCj 11 คำถามที่พบบ่อยของ Bjarne Stroustrup :
enum class
ES ( "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
พวกเขาไม่ได้แปลงโดยปริยายข้อได้เปรียบพื้นฐานของการใช้คลาส 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??)
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
แก้ปัญหาเนมสเปซได้อย่างง่ายดาย อาร์กิวเมนต์เนมสเปซเป็นหนึ่งในสามข้อที่กล่าวถึงในที่นี้ซึ่งฉันไม่ได้ซื้อเลย
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
เทียบได้กับระดับ enum class Color1 { RED, GREEN, BLUE }
Enum: การเข้าถึงคล้าย: COLOR1_RED
vs Color1::RED
แต่รุ่น Enum ต้องการให้คุณพิมพ์ "COLOR1" ในแต่ละค่าซึ่งให้พื้นที่เพิ่มเติมสำหรับการพิมพ์ผิดซึ่งลักษณะการทำงานของเนมสเปซของคลาส enum หลีกเลี่ยง
enum Color1
ซึ่งคอมไพเลอร์ไม่สามารถตรวจจับได้เนื่องจากมีแนวโน้มว่าจะยังคงเป็นชื่อ 'ถูกต้อง' ถ้าผมเขียนRED
, GREEN
และอื่น ๆ โดยใช้ชั้น enum กว่ามันไม่สามารถแก้ไขไปenum Banana
เพราะมันต้องการให้คุณระบุColor1::RED
เพื่อเข้าถึงค่า (อาร์กิวเมนต์ namespace) ที่ ยังคงมีเวลาที่ดีที่จะใช้enum
แต่พฤติกรรม namespace ของenum class
มักจะมีประโยชน์มาก
การแจกแจงใช้เพื่อแสดงชุดของค่าจำนวนเต็ม
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
คำถามที่พบบ่อย 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 ++ 20 แก้ปัญหาอย่างใดอย่างหนึ่งที่enum class
มี: verbosity จินตนาการถึงสมมุติฐานenum class
, Color
.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
นี้จะ verbose เมื่อเทียบกับธรรมดารูปแบบที่ชื่ออยู่ในขอบเขตทั่วโลกและดังนั้นจึงไม่จำเป็นต้องนำหน้าด้วยenum
Color::
อย่างไรก็ตามใน C ++ 20 เราสามารถใช้using enum
เพื่อแนะนำชื่อทั้งหมดใน enum กับขอบเขตปัจจุบันการแก้ปัญหา
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
enum class
ดังนั้นตอนนี้มีเหตุผลที่จะไม่ใช้
เนื่องจากดังที่ได้กล่าวไว้ในคำตอบอื่น ๆ class enum ไม่สามารถแปลงเป็น int / bool ได้โดยปริยายดังนั้นจึงช่วยหลีกเลี่ยงโค้ด buggy เช่น:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
สิ่งหนึ่งที่ไม่ได้กล่าวถึงอย่างชัดเจน - คุณลักษณะขอบเขตให้ตัวเลือกให้คุณมีชื่อเดียวกันสำหรับวิธีการ 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
};