Tại sao lớp enum được ưa thích hơn enum đồng bằng?


429

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ì?


57
Khi ai đó tuyên bố rằng một số cấu trúc lập trình là "xấu xa", họ đang cố gắng ngăn cản bạn nghĩ cho chính mình.
Pete Becker

3
@NicolBolas: Đây là một câu hỏi khó hiểu hơn để cung cấp câu trả lời Câu hỏi thường gặp (liệu đây có phải là câu hỏi thường gặp hay không là một câu chuyện khác).
David Rodríguez - dribeas

@David, có một cuộc thảo luận cho dù đây có phải là Câu hỏi thường gặp hay không sẽ bắt đầu ở đây . Đầu vào chào mừng.
sbi

17
@PeteBecker Đôi khi, họ chỉ cố gắng bảo vệ bạn khỏi chính bạn.
piccy

geeksforgeeks.org/... Đây cũng là một nơi tốt để hiểu enumvs enum class.
mr_azad

Câu trả lời:


472

C ++ có hai loại enum:

  1. enum classes
  2. Plain enums

Dướ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 classtê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 enumhoặ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;

}

Phần kết luận:

enum classes 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.


7
Ví dụ hay ... có cách nào để kết hợp loại an toàn của phiên bản lớp với quảng cáo không gian tên của phiên bản enum không? Đó là, nếu tôi có một lớp học Avớ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 == onlinekiểm tra bên trong Athay vì state == State::online... điều đó có thể không?
đánh dấu

31
Không. Quảng cáo không gian tên là một điều xấu và một nửa lý do cho enum classviệc loại bỏ nó.
Cún con

10
Trong C ++ 11, bạn cũng có thể sử dụng các enum được gõ rõ ràng, như enum Animal: unsign int {dog, Deer, cat, bird}
Blasius Secundus

3
@Cat Plus Plus Tôi hiểu rằng @Oleksiy nói nó tệ. Câu hỏi của tôi không phải là nếu Oleksiy nghĩ nó tệ. Câu hỏi của tôi là một yêu cầu để chi tiết những gì xấu về nó. Cụ thể, tại sao Oleksiy, ví dụ, xem xét xấu về Color color = Color::red.
chux - Phục hồi Monica

9
@Cat Plus Plus Vì vậy, ví dụ xấu không xảy ra cho đến 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.
chux - Phục hồi Monica

248

Từ câu hỏi thường gặp về C ++ 11 của Bjarne Stroustrup :

Các enum classes ( "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 enumkhô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" enumphả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 charhoặc một loại số nguyên có dấu / không dấu.

Đây là một mô tả rộng về enumloạ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 enumvà đô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_FRUITSgiá 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 charhoặc inthoặ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_FORCE8là 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_FRUITSsẽ 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ã enumcó thể phá vỡ liên quan.

Vì C ++ 11 có thể chỉ định loại cơ bản cho enumenum 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 classsự lựa chọn tốt hơn?:

  • Họ không chuyển đổi hoàn toàn sang int.
  • Họ không làm ô nhiễm không gian tên xung quanh.
  • Họ có thể được tuyên bố trước.

1
Tôi cho rằng chúng ta cũng có thể hạn chế loại cơ sở enum trong các enum thông thường, miễn là chúng ta có C ++ 11
Sagar Padhye

11
Xin lỗi, nhưng câu trả lời này là sai. "Lớp enum" không liên quan gì đến khả năng chỉ định loại. Đó là một tính năng độc lập tồn tại cả cho enum thông thường và cho các lớp enum.
rdb

14
Đây là thỏa thuận: * Các lớp Enum là một tính năng mới trong C ++ 11. * Enum đánh máy là một tính năng mới trong C ++ 11. Đây là hai tính năng mới không liên quan riêng biệt trong C ++ 11. Bạn có thể sử dụng cả hai hoặc bạn có thể sử dụng hoặc không.
rdb

2
Tôi nghĩ rằng Alex Allain cung cấp lời giải thích đơn giản đầy đủ nhất mà tôi đã thấy trong blog này tại [ cprogramming.com/c++11/ . Enum truyền thống là tốt cho việc sử dụng tên thay vì giá trị số nguyên và tránh sử dụng #defines tiền xử lý, đó là một điều tốt - nó thêm rõ ràng. lớp enum loại bỏ khái niệm giá trị số của bộ liệt kê và giới thiệu phạm vi và kiểu gõ mạnh làm tăng (tốt, có thể tăng :-) tính chính xác của chương trình. Nó di chuyển bạn một bước gần hơn để suy nghĩ hướng đối tượng.
Jon Spencer

2
Bên cạnh, nó luôn gây cười khi bạn đang xem lại mã và đột nhiên One Piece xảy ra.
Thời gian của Justin - Phục hồi lại

47

Ư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 Color1hay Color2như 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??)

1
@Oleksiy Ohh Tôi không đọc đúng câu hỏi của bạn. Xem xét như là một tiện ích bổ sung cho những người không biết.
Saksham

Ổn mà! Tôi gần như quên mất điều này
Oleksiy

tất nhiên, bạn sẽ viết 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.
Jo So

2
@Jo Vậy giải pháp đó là một cách giải quyết không cần thiết. Enum: 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_REDvs 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.
cdgraham

2
Hãy sử dụng những lời chỉ trích mang tính xây dựng . Khi tôi nói nhiều chỗ hơn cho lỗi chính tả, ý tôi là khi ban đầu bạn xác định các giá trị enum Color1mà 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, GREENvà cứ thế sử dụng một lớp enum, thì nó không thể giải quyết được enum Bananavì 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 classthường có thể rất có lợi.
cdgraham

20

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 classtừ khóa sau khi enumquy đị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 enumgiú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

7

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;
}

C ++ 11 cho phép các enum "không phải lớp" cũng được . Các vấn đề ô nhiễm không gian tên, vv, vẫn còn tồn tại. Hãy xem các câu trả lời có liên quan đã tồn tại một thời gian dài trước câu trả lời này ..
user2864740

7
  1. không hoàn toàn chuyển đổi sang int
  2. có thể chọn loại nền tảng
  3. Không gian tên ENUM để tránh ô nhiễm xảy ra
  4. So với lớp bình thường, có thể được khai báo chuyển tiếp, nhưng không có phương thức

1

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

2
Để hoàn thành nhận xét trước đây của tôi, lưu ý rằng gcc hiện có cảnh báo có tên -Wint-in-bool-bối cảnh sẽ bắt chính xác loại lỗi này.
Arnaud

1

Đ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 classcó: 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 enumbiế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.


0

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
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.