Mengapa kelas enum lebih disukai daripada enum biasa?

429

Saya mendengar beberapa orang merekomendasikan untuk menggunakan kelas enum di C ++ karena keamanan tipe mereka .

Tapi, apa arti sebenarnya?

Oleksiy
sumber
57
Ketika seseorang mengklaim bahwa beberapa pemrograman pemrograman adalah "jahat" mereka mencoba untuk mencegah Anda berpikir sendiri.
Pete Becker
3
@NicolBolas: Ini lebih merupakan pertanyaan retoris untuk memberikan jawaban FAQ (apakah ini benar-benar sering ditanyakan adalah cerita yang berbeda).
David Rodríguez - dribeas
@ David, ada diskusi apakah ini harus menjadi FAQ atau tidak yang dimulai di sini . Masukan sambutan.
sbi
17
@PeteBecker Terkadang mereka hanya berusaha melindungi Anda dari diri sendiri.
piccy
geeksforgeeks.org/... ini juga merupakan tempat yang baik untuk memahami enumvs enum class.
mr_azad

Jawaban:

473

C ++ memiliki dua jenis enum:

  1. enum classes
  2. enumS polos

Berikut adalah beberapa contoh cara mendeklarasikannya:

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

Apa perbedaan antara keduanya?

  • enum classnama es - enumerator adalah lokal untuk enum dan nilainya tidak secara implisit dikonversi ke tipe lain (seperti yang lain enumatau int)

  • Plain enums - di mana nama enumerator berada dalam cakupan yang sama dengan enum dan nilainya secara implisit dikonversi menjadi bilangan bulat dan tipe lainnya

Contoh:

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;

}

Kesimpulan:

enum classHarus lebih disukai karena mereka menyebabkan lebih sedikit kejutan yang berpotensi menyebabkan bug.

Oleksiy
sumber
7
Contoh yang bagus ... apakah ada cara untuk menggabungkan jenis keamanan dari versi kelas dengan promosi namespace dari versi enum? Yaitu, jika saya memiliki kelas Adengan negara, dan saya membuat enum class State { online, offline };sebagai anak kelas A, saya ingin melakukan state == onlinepemeriksaan di dalam Adaripada state == State::online... apakah itu mungkin?
tandai
31
Nggak. Promosi namespace adalah Bad Thing ™ dan setengah dari justifikasi enum classadalah untuk menghilangkannya.
Puppy
10
Di C ++ 11, Anda dapat menggunakan enum yang diketik secara eksplisit juga, seperti enum Animal: unsigned int {dog, deer, cat, bird}
Blasius Secundus
3
@Cat Plus Plus Saya mengerti bahwa @Oleksiy mengatakan itu buruk. Pertanyaan saya bukanlah apakah Oleksiy menganggapnya buruk. Pertanyaan saya adalah permintaan untuk menjelaskan apa yang buruk tentangnya. Secara khusus, mengapa Oleksiy, misalnya, menganggap buruk Color color = Color::red.
chux - Reinstate Monica
9
@Cat Plus Plus Jadi contoh buruk tidak terjadi sampai if (color == Card::red_card)baris, 4 baris lebih lambat dari komentar (yang saya lihat sekarang berlaku untuk paruh pertama blok.) 2 baris blok memberikan contoh buruk . 3 baris pertama tidak masalah. "Seluruh blok adalah mengapa enums polos buruk" melemparkan saya karena saya pikir Anda berarti ada yang salah dengan itu juga. Saya mengerti sekarang, itu hanya set-up. Bagaimanapun, terima kasih atas umpan baliknya.
chux - Reinstate Monica
248

Dari FAQ C ++ 11 Bjarne Stroustrup :

The enum classes ( "enum baru", "enum kuat") alamat tiga masalah dengan tradisional C ++ mantri:

  • enum konvensional secara implisit dikonversi ke int, menyebabkan kesalahan ketika seseorang tidak ingin enumerasi bertindak sebagai integer.
  • Enum konvensional mengekspor enumerator mereka ke ruang lingkup di sekitarnya, menyebabkan bentrokan nama.
  • tipe yang mendasari suatu enumtidak dapat ditentukan, menyebabkan kebingungan, masalah kompatibilitas, dan membuat pernyataan maju tidak mungkin.

Enum baru adalah "kelas enum" karena mereka menggabungkan aspek-aspek enumerasi tradisional (nilai-nilai nama) dengan aspek-aspek kelas (anggota lingkup dan tidak adanya konversi).

Jadi, seperti yang disebutkan oleh pengguna lain, "enum yang kuat" akan membuat kode lebih aman.

Tipe yang mendasari "klasik" enumharus merupakan tipe integer yang cukup besar untuk memenuhi semua nilai dari enum; ini biasanya sebuah int. Juga setiap tipe yang disebutkan harus kompatibel dengan charatau tipe integer yang ditandatangani / tidak ditandatangani.

Ini adalah deskripsi luas tentang apa yang enumharus menjadi tipe yang mendasarinya, sehingga setiap kompiler akan mengambil keputusan sendiri tentang tipe yang mendasarinya enumdan kadang-kadang hasilnya bisa mengejutkan.

Sebagai contoh, saya telah melihat kode seperti ini beberapa kali:

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

Dalam kode di atas, beberapa pembuat kode naif berpikir bahwa kompiler akan menyimpan E_MY_FAVOURITE_FRUITSnilai - nilai ke dalam tipe 8bit yang tidak ditandatangani ... tetapi tidak ada garansi tentang hal itu: kompiler dapat memilih unsigned charatau intatau short, salah satu dari jenis itu cukup besar untuk memenuhi semua nilai yang terlihat di enum. Menambahkan bidang E_MY_FAVOURITE_FRUITS_FORCE8adalah beban dan tidak memaksa kompiler untuk membuat pilihan apa pun tentang tipe yang mendasarinya enum.

Jika ada beberapa bagian dari kode yang bergantung pada ukuran tipe dan / atau mengasumsikan bahwa E_MY_FAVOURITE_FRUITSakan menjadi lebar (misalnya: rutinisasi serialisasi) kode ini dapat berperilaku dalam beberapa cara aneh tergantung pada pemikiran kompiler.

Dan untuk membuat keadaan menjadi lebih buruk, jika beberapa rekan kerja secara sembrono menambahkan nilai baru pada kita enum:

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

Kompiler tidak mengeluh tentang itu! Itu hanya mengubah ukuran jenis agar sesuai dengan semua nilai enum(dengan asumsi bahwa kompiler menggunakan jenis terkecil mungkin, yang merupakan asumsi yang tidak bisa kita lakukan). Penambahan sederhana dan ceroboh ini enumdapat secara halus memecahkan kode terkait.

Karena C ++ 11 dimungkinkan untuk menentukan tipe yang mendasarinya untuk enumdan enum class(terima kasih rdb ) sehingga masalah ini diatasi dengan rapi:

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

Menentukan tipe yang mendasarinya jika bidang memiliki ekspresi di luar rentang tipe ini kompiler akan mengeluh bukannya mengubah tipe yang mendasarinya.

Saya pikir ini adalah peningkatan keamanan yang baik.

Jadi, mengapa kelas enum lebih disukai daripada enum biasa? , jika kita dapat memilih tipe yang mendasari untuk scoped ( enum class) dan unscoped ( enum) enum apa lagi yang membuat enum classpilihan yang lebih baik ?:

  • Mereka tidak mengkonversi secara implisit ke int.
  • Mereka tidak mencemari namespace sekitarnya.
  • Mereka dapat dideklarasikan ke depan.
PaperBirdMaster
sumber
1
Saya kira kita dapat membatasi tipe basis enum untuk enum reguler juga, selama kita memiliki C ++ 11
Sagar Padhye
11
Maaf, tapi jawaban ini salah. "enum class" tidak ada hubungannya dengan kemampuan untuk menentukan jenisnya. Itu fitur independen yang ada untuk enum reguler dan untuk kelas enum.
rdb
14
Ini masalahnya: * Kelas Enum adalah fitur baru di C ++ 11. * Enums yang diketik adalah fitur baru di C ++ 11. Ini adalah dua fitur baru yang tidak terkait terpisah di C ++ 11. Anda dapat menggunakan keduanya, atau Anda bisa menggunakan keduanya, atau tidak keduanya.
rdb
2
Saya pikir Alex Allain memberikan penjelasan sederhana paling lengkap yang belum saya lihat di blog ini di [ cprogramming.com/c++11/… . Enum tradisional baik untuk menggunakan nama bukan nilai integer dan menghindari menggunakan preprocessor #defines, yang merupakan Good Thing - itu menambahkan kejelasan. enum class menghapus konsep nilai numerik enumerator, dan memperkenalkan lingkup dan pengetikan yang kuat yang meningkat (well, dapat meningkatkan :-) kebenaran program. Ini menggerakkan Anda selangkah lebih dekat ke pemikiran berorientasi objek.
Jon Spencer
2
Selain itu, selalu lucu ketika Anda meninjau kode dan tiba-tiba One Piece terjadi.
Justin Time - Pasang kembali Monica
47

Keuntungan dasar menggunakan kelas enum daripada enum normal adalah bahwa Anda mungkin memiliki variabel enum yang sama untuk 2 enum yang berbeda dan masih dapat menyelesaikannya (yang telah disebut sebagai tipe safe oleh OP)

Untuk misalnya:

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

Adapun enum dasar, kompiler tidak akan dapat membedakan apakah redmerujuk ke tipe Color1atau Color2seperti dalam hte di bawah pernyataan.

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)
Saksham
sumber
1
@Oleksiy Ohh saya tidak membaca pertanyaan Anda dengan benar. Pertimbangkan adalah sebagai tambahan bagi mereka yang tidak tahu.
Saksham
tidak apa-apa! Saya hampir lupa tentang ini
Oleksiy
tentu saja, Anda akan menulis enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }, dengan mudah menghindari masalah namespace. Argumen namespace adalah salah satu dari tiga yang disebutkan di sini yang tidak saya beli sama sekali.
Jo So
2
@ Jo So Solusi itu adalah solusi yang tidak perlu. Enum: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }sebanding dengan kelas Enum: enum class Color1 { RED, GREEN, BLUE }. Mengakses sama: COLOR1_REDvs Color1::RED, tetapi versi Enum mengharuskan Anda mengetik "COLOR1" di setiap nilai, yang memberikan lebih banyak ruang untuk kesalahan ketik, yang perilaku namespace dari kelas enum menghindari.
cdgraham
2
Silakan gunakan kritik yang membangun . Ketika saya mengatakan lebih banyak ruang untuk kesalahan ketik, maksud saya ketika Anda awalnya mendefinisikan nilai-nilai enum Color1, yang tidak bisa ditangkap oleh kompiler karena kemungkinan masih berupa nama 'valid'. Jika saya menulis RED, GREENdan seterusnya menggunakan kelas enum, maka tidak dapat menyelesaikan enum Bananakarena mengharuskan Anda menentukan Color1::REDuntuk mengakses nilai (argumen namespace). Masih ada waktu yang baik untuk digunakan enum, tetapi perilaku namespace dari suatu enum classseringkali bisa sangat bermanfaat.
cdgraham
20

Enumerasi digunakan untuk mewakili satu set nilai integer.

Kata classkunci setelah enummenentukan bahwa enumerasi diketik dengan kuat dan enumeratornya dicakup. Dengan cara ini enumkelas mencegah penyalahgunaan konstanta secara tidak sengaja.

Sebagai contoh:

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

Di sini kita tidak bisa mencampurkan nilai Hewan dan Hewan Piaraan.

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

C ++ 11 FAQ menyebutkan poin-poin di bawah ini:

enum konvensional secara implisit dikonversi ke int, menyebabkan kesalahan ketika seseorang tidak ingin enumerasi bertindak sebagai integer.

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 konvensional mengekspor enumerator mereka ke ruang lingkup di sekitarnya, menyebabkan bentrokan nama.

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

Jenis enum yang mendasarinya tidak dapat ditentukan, menyebabkan kebingungan, masalah kompatibilitas, dan membuat pernyataan maju menjadi tidak mungkin.

// 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;
}
Swapnil
sumber
C ++ 11 memungkinkan enum "non-kelas" untuk diketik juga. Masalah polusi namespace, dll, masih ada. Lihatlah jawaban yang relevan yang sudah ada jauh sebelum ini ..
user2864740
7
  1. jangan secara implisit mengkonversi ke int
  2. dapat memilih jenis yang mendasari
  3. ENUM namespace untuk menghindari polusi terjadi
  4. Dibandingkan dengan kelas normal, dapat dinyatakan maju, tetapi tidak memiliki metode
Qinsheng Zhang
sumber
2

Perlu dicatat, di atas semua jawaban lain ini, C ++ 20 memecahkan salah satu masalah yang enum classada: verbosity. Membayangkan hipotetis sebuah enum class, Color.

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

Ini verbose dibandingkan dengan enumvariasi biasa , di mana nama-nama berada dalam lingkup global dan karenanya tidak perlu diawali dengan Color::.

Namun, di C ++ 20 kita bisa menggunakan using enumuntuk memperkenalkan semua nama dalam enum ke ruang lingkup saat ini, menyelesaikan masalah.

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

Jadi sekarang, tidak ada alasan untuk tidak menggunakannya enum class.

Tom VH
sumber
1

Karena, seperti yang dikatakan dalam jawaban lain, class enum tidak secara implisit dapat dikonversi ke int / bool, itu juga membantu menghindari kode buggy seperti:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Arnaud
sumber
2
Untuk melengkapi komentar saya sebelumnya, perhatikan bahwa gcc sekarang memiliki peringatan yang disebut -Wint-in-bool-context yang akan menangkap kesalahan semacam ini.
Arnaud
0

Satu hal yang belum disebutkan secara eksplisit - fitur lingkup memberi Anda opsi untuk memiliki nama yang sama untuk metode enum dan kelas. Contohnya:

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
};
Miro Kropacek
sumber