Tôi đã nghe một vài người khuyên nên sử dụng các lớp enum trong C ++ vì loại an toàn của họ .
Nhưng điều đó thực sự có ý nghĩa gì?
Tôi đã nghe một vài người khuyên nên sử dụng các lớp enum trong C ++ vì loại an toàn của họ .
Nhưng điều đó thực sự có ý nghĩa gì?
Câu trả lời:
C ++ có hai loại enum
:
enum class
esenum
sDưới đây là một vài ví dụ về cách khai báo chúng:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
Sự khác biệt giữa hai là gì?
enum class
tên es - enumerator là cục bộ của enum và giá trị của chúng không hoàn toàn chuyển đổi sang các loại khác (như loại khác enum
hoặc int
)
Đồng bằng enum
- trong đó tên của điều tra viên có cùng phạm vi với enum và giá trị của chúng hoàn toàn chuyển đổi thành số nguyên và các loại khác
Thí dụ:
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 nên được ưu tiên bởi vì chúng gây ra ít bất ngờ hơn có khả năng dẫn đến lỗi.
A
với trạng thái và tôi tạo ra enum class State { online, offline };
một đứa trẻ của lớp A
, tôi muốn state == online
kiểm tra bên trong A
thay vì state == State::online
... điều đó có thể không?
enum class
việc loại bỏ nó.
Color color = Color::red
.
if (color == Card::red_card)
dòng, muộn hơn 4 dòng so với nhận xét (mà tôi thấy bây giờ áp dụng cho nửa đầu của khối.) 2 dòng của khối đưa ra các ví dụ xấu . 3 dòng đầu tiên không phải là một vấn đề. "Toàn bộ khối là lý do tại sao enums đơn giản là xấu" đã ném tôi khi tôi nghĩ rằng bạn có nghĩa là có gì đó không đúng với những điều đó quá. Tôi thấy bây giờ, nó chỉ là một thiết lập. Trong mọi trường hợp, cảm ơn cho thông tin phản hồi.
Từ câu hỏi thường gặp về C ++ 11 của Bjarne Stroustrup :
Các
enum class
es ( "enums mới", "enums mạnh") địa chỉ ba vấn đề với kiểu liệt kê truyền thống C ++:
- enum thông thường ngầm chuyển đổi thành int, gây ra lỗi khi ai đó không muốn liệt kê hoạt động như một số nguyên.
- enum thông thường xuất các điều tra viên của họ đến phạm vi xung quanh, gây ra xung đột tên.
- loại cơ bản của một
enum
không thể được chỉ định, gây nhầm lẫn, vấn đề tương thích và không thể khai báo chuyển tiếp.Các enum mới là "lớp enum" bởi vì chúng kết hợp các khía cạnh của bảng liệt kê truyền thống (tên giá trị) với các khía cạnh của các lớp (thành viên trong phạm vi và không có chuyển đổi).
Vì vậy, như được đề cập bởi những người dùng khác, "enum mạnh" sẽ giúp mã an toàn hơn.
Kiểu cơ bản của "cổ điển" enum
phải là kiểu số nguyên đủ lớn để phù hợp với tất cả các giá trị của enum
; điều này thường là một int
. Ngoài ra, mỗi loại liệt kê sẽ tương thích với char
hoặc một loại số nguyên có dấu / không dấu.
Đây là một mô tả rộng về enum
loại cơ bản phải là gì, vì vậy mỗi trình biên dịch sẽ tự mình đưa ra quyết định về loại cơ bản của cổ điển enum
và đôi khi kết quả có thể gây ngạc nhiên.
Ví dụ: tôi đã thấy mã như thế này rất nhiều lần:
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?
};
Trong đoạn mã trên, một số coder ngây thơ đang nghĩ rằng trình biên dịch sẽ lưu trữ các E_MY_FAVOURITE_FRUITS
giá trị vào một loại 8bit unsigned ... nhưng không có bảo hành về nó: trình biên dịch có thể chọn unsigned char
hoặc int
hoặc short
, bất kỳ của những loại là đủ lớn để phù hợp với tất cả các các giá trị nhìn thấy trong enum
. Thêm trường E_MY_FAVOURITE_FRUITS_FORCE8
là một gánh nặng và không buộc trình biên dịch đưa ra bất kỳ loại lựa chọn nào về loại cơ bản của enum
.
Nếu có một số đoạn mã dựa trên kích thước loại và / hoặc giả định E_MY_FAVOURITE_FRUITS
sẽ có chiều rộng nào đó (ví dụ: thói quen tuần tự hóa) thì mã này có thể hành xử theo một số cách kỳ lạ tùy thuộc vào suy nghĩ của trình biên dịch.
Và để làm cho vấn đề tồi tệ hơn, nếu một số đồng nghiệp thêm bất cẩn một giá trị mới cho chúng tôi enum
:
E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits
Trình biên dịch không phàn nàn về điều đó! Nó chỉ thay đổi kích thước loại để phù hợp với tất cả các giá trị của enum
(giả sử rằng trình biên dịch đang sử dụng loại nhỏ nhất có thể, đó là một giả định mà chúng ta không thể làm được). Sự bổ sung đơn giản và bất cẩn này vào mã enum
có thể phá vỡ liên quan.
Vì C ++ 11 có thể chỉ định loại cơ bản cho enum
và enum class
(cảm ơn rdb ) nên vấn đề này được giải quyết gọn gàng:
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
};
Chỉ định loại bên dưới nếu một trường có biểu thức nằm ngoài phạm vi của loại này, trình biên dịch sẽ khiếu nại thay vì thay đổi loại bên dưới.
Tôi nghĩ rằng đây là một cải tiến an toàn tốt.
Vậy tại sao lớp enum được ưa thích hơn enum đồng bằng? , nếu chúng ta có thể chọn loại cơ bản cho enum scoped ( enum class
) và unscoped ( enum
) thì điều gì khác làm cho enum class
sự lựa chọn tốt hơn?:
int
.Ưu điểm cơ bản của việc sử dụng lớp enum so với enum bình thường là bạn có thể có cùng một biến enum cho 2 enum khác nhau và vẫn có thể giải quyết chúng (được OP đề cập là loại an toàn )
Ví dụ:
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 };
Đối với các enum cơ bản, trình biên dịch sẽ không thể phân biệt red
được là tham chiếu đến kiểu Color1
hay Color2
như trong câu lệnh hte bên dưới.
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 }
, dễ dàng làm giảm các vấn đề về không gian tên. Đối số không gian tên là một trong ba đối số được đề cập ở đây mà tôi hoàn toàn không mua.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
có thể so sánh với lớp Enum : enum class Color1 { RED, GREEN, BLUE }
. Truy cập tương tự: COLOR1_RED
vs Color1::RED
, nhưng phiên bản Enum yêu cầu bạn nhập "COLOR1" trong mỗi giá trị, điều này mang lại nhiều chỗ hơn cho lỗi chính tả, hành vi không gian tên của lớp enum tránh được.
enum Color1
mà trình biên dịch không thể bắt được vì nó có thể vẫn là một tên 'hợp lệ'. Nếu tôi viết RED
, GREEN
và cứ thế sử dụng một lớp enum, thì nó không thể giải quyết được enum Banana
vì nó yêu cầu bạn chỉ định Color1::RED
để truy cập giá trị (đối số không gian tên). Vẫn có những thời điểm tốt để sử dụng enum
, nhưng hành vi không gian tên của một enum class
thường có thể rất có lợi.
Số liệt kê được sử dụng để biểu diễn một tập hợp các giá trị nguyên.
Các class
từ khóa sau khi enum
quy định cụ thể rằng liệt kê được gõ mạnh và điều tra viên của nó được scoped. Cách này enum
giúp ngăn ngừa việc lạm dụng các hằng số.
Ví dụ:
enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};
Ở đây chúng ta không thể trộn lẫn các giá trị Động vật và Thú cưng.
Animal a = Dog; // Error: which DOG?
Animal a = Pets::Dog // Pets::Dog is not an Animal
C ++ 11 FAQ đề cập dưới đây điểm:
enum thông thường ngầm chuyển đổi thành int, gây ra lỗi khi ai đó không muốn liệt kê hoạt động như một số nguyên.
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;
}
enum thông thường xuất các điều tra viên của họ đến phạm vi xung quanh, gây ra xung đột tên.
// 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
};
Loại cơ bản của một enum không thể được chỉ định, gây nhầm lẫn, các vấn đề tương thích và không thể khai báo chuyển tiếp.
// 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;
}
Bởi vì, như đã nói trong các câu trả lời khác, lớp enum không hoàn toàn có thể chuyển đổi thành int / bool, nó cũng giúp tránh mã lỗi như:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Điều đáng chú ý, trên đầu những câu trả lời khác, C ++ 20 giải quyết một trong những vấn đề enum class
có: tính dài dòng. Tưởng tượng một giả thuyết enum class
, Color
.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
Đây là dài dòng so với enum
biến thể đơn giản , trong đó các tên nằm trong phạm vi toàn cầu và do đó không cần phải có tiền tố Color::
.
Tuy nhiên, trong C ++ 20, chúng ta có thể sử dụng using enum
để giới thiệu tất cả các tên trong một enum cho phạm vi hiện tại, giải quyết vấn đề.
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
Vì vậy, bây giờ, không có lý do để không sử dụng enum class
.
Một điều chưa được đề cập rõ ràng - tính năng phạm vi cung cấp cho bạn một tùy chọn để có cùng tên cho một phương thức enum và class. Ví dụ:
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
};