Saya mendengar beberapa orang merekomendasikan untuk menggunakan kelas enum di C ++ karena keamanan tipe mereka .
Tapi, apa arti sebenarnya?
Saya mendengar beberapa orang merekomendasikan untuk menggunakan kelas enum di C ++ karena keamanan tipe mereka .
Tapi, apa arti sebenarnya?
Jawaban:
C ++ memiliki dua jenis enum:
enum classesenumS polosBerikut 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;
}
enum classHarus lebih disukai karena mereka menyebabkan lebih sedikit kejutan yang berpotensi menyebabkan bug.
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?
enum classadalah untuk menghilangkannya.
Color color = Color::red.
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.
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 ?:
int.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??)
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.
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.
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.
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
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;
}
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.
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
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
};