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 class
esenum
S 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 class
nama es - enumerator adalah lokal untuk enum dan nilainya tidak secara implisit dikonversi ke tipe lain (seperti yang lain enum
atau int
)
Plain enum
s - 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 class
Harus lebih disukai karena mereka menyebabkan lebih sedikit kejutan yang berpotensi menyebabkan bug.
A
dengan negara, dan saya membuat enum class State { online, offline };
sebagai anak kelas A
, saya ingin melakukan state == online
pemeriksaan di dalam A
daripada state == State::online
... apakah itu mungkin?
enum class
adalah 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 class
es ( "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
enum
tidak 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" enum
harus merupakan tipe integer yang cukup besar untuk memenuhi semua nilai dari enum
; ini biasanya sebuah int
. Juga setiap tipe yang disebutkan harus kompatibel dengan char
atau tipe integer yang ditandatangani / tidak ditandatangani.
Ini adalah deskripsi luas tentang apa yang enum
harus menjadi tipe yang mendasarinya, sehingga setiap kompiler akan mengambil keputusan sendiri tentang tipe yang mendasarinya enum
dan 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_FRUITS
nilai - nilai ke dalam tipe 8bit yang tidak ditandatangani ... tetapi tidak ada garansi tentang hal itu: kompiler dapat memilih unsigned char
atau int
atau short
, salah satu dari jenis itu cukup besar untuk memenuhi semua nilai yang terlihat di enum
. Menambahkan bidang E_MY_FAVOURITE_FRUITS_FORCE8
adalah 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_FRUITS
akan 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 enum
dapat secara halus memecahkan kode terkait.
Karena C ++ 11 dimungkinkan untuk menentukan tipe yang mendasarinya untuk enum
dan 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 class
pilihan 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 red
merujuk ke tipe Color1
atau Color2
seperti 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_RED
vs 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
, GREEN
dan seterusnya menggunakan kelas enum, maka tidak dapat menyelesaikan enum Banana
karena mengharuskan Anda menentukan Color1::RED
untuk mengakses nilai (argumen namespace). Masih ada waktu yang baik untuk digunakan enum
, tetapi perilaku namespace dari suatu enum class
seringkali bisa sangat bermanfaat.
Enumerasi digunakan untuk mewakili satu set nilai integer.
Kata class
kunci setelah enum
menentukan bahwa enumerasi diketik dengan kuat dan enumeratornya dicakup. Dengan cara ini enum
kelas 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 class
ada: 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 enum
variasi biasa , di mana nama-nama berada dalam lingkup global dan karenanya tidak perlu diawali dengan Color::
.
Namun, di C ++ 20 kita bisa menggunakan using enum
untuk 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
};