Enum sınıfı neden düz enuma tercih edilir?


429

Enum kullanmayı tavsiye eden birkaç kişi duydum tür güvenliği nedeniyle C ++ sınıfları .

Ama bu gerçekten ne anlama geliyor?


57
Birisi bazı programlama yapılarının "kötü" olduğunu iddia ettiğinde, sizi kendiniz için düşünmekten vazgeçirmeye çalışırlar.
Pete Becker

3
@NicolBolas: Bu daha bir SSS cevap sağlamak için bir retorik soru (bu gerçekten olup olmadığını frequenty sorulan farklı bir hikaye).
David Rodríguez - dribeas

@David, bunun bir SSS olması gerekip gerekmediği, burada başlayan bir tartışma var . Girdi hoş geldiniz.
sbi

17
@PeteBecker Bazen sizi sadece kendinizden korumaya çalışıyorlar .
piccy

geeksforgeeks.org/… Bu aynı zamanda enumvs anlamak için iyi bir yerdir enum class.
mr_azad

Yanıtlar:


472

C ++ iki çeşittir enum:

  1. enum classes
  2. Düz enums

Onları nasıl ilan edeceğinize dair birkaç örnek:

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

İkisi arasındaki fark nedir?

  • enum classes - listeleyicisi isimleri yerel enum ve değerleri yok değil örtülü (başka gibi diğer türlerine dönüştürmek enumya int)

  • Düz enums - numaralandırıcı adlarının enum ile aynı kapsamda olduğu ve değerlerinin dolaylı olarak tamsayılara ve diğer türlere dönüştüğü

Misal:

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;

}

Sonuç:

enum classpotansiyel olarak hatalara yol açabilecek daha az sürprize neden oldukları için tercih edilmelidir.


7
İyi örnek ... sınıf versiyonunun tip güvenliğini enum versiyonunun isim-alanı tanıtımıyla birleştirmenin bir yolu var mı? Yani, Adevletli bir sınıfım varsa ve sınıfın enum class State { online, offline };çocuğu olarak bir sınıf oluşturursam A, bunun yerine state == onlineiçeride kontroller yapmak istiyorum ... bu mümkün mü? Astate == State::online
mark

31
Hayır! İsim alanı tanıtımı bir Bad Thing ™ ve bunun gerekçesinin yarısı enum classonu ortadan kaldırmaktı.
Köpek yavrusu

10
C ++ 11'de, enum Animal gibi açıkça yazılan numaralandırmalar da kullanabilirsiniz: unsigned int {köpek, geyik, kedi, kuş}
Blasius Secundus

3
@Cat Plus Plus @Oleksiy'in kötü olduğunu söylediğini anlıyorum. Sorum Oleksiy'in kötü olduğunu düşünmeseydi. Sorum bu konuda neyin kötü olduğunu detaylandırma isteğiydi . Özellikle Oleksiy , niçin , bu konuda kötü düşünüyor Color color = Color::red?
chux - Monica adlı kullanıcıdan

9
Artı Artı So @Cat Örnekteki kötü oluşmaz kadar if (color == Card::red_card)hat, 4 satır sonra açıklama daha (Şimdi gördüğüm bloğun ilk yarısında için de geçerlidir.) Bloğun 2 satır verir kötü örnekler. İlk 3 satır sorun değil. "Tüm blok neden düz numaralandırmalar kötü" beni de attı, ben de bu konuda bir şeyler yanlış olduğunu düşündüm. Şimdi görüyorum, bu sadece bir kurulum. Her durumda, geri bildirim için teşekkürler.
chux - Monica'yı geri yükle

248

Gönderen Bjarne Stroustrup'un C ++ 11 SSS :

enum classEs ( "Yeni çeteleler", "güçlü çeteleler") adresi geleneksel C ++ numaralandırma ile üç sorunlar:

  • konvansiyonel numaralandırmalar dolaylı olarak int'e dönüştürülür ve bir numaralandırma bir tamsayı gibi davranmak istemediğinde hatalara neden olur.
  • konvansiyonel numaralandırmalar numaralandırıcılarını çevre kapsamına ihraç ederek isim çatışmalarına neden olmaktadır.
  • altta yatan tür enumbelirtilemez, karışıklığa, uyumluluk sorunlarına neden olur ve ileri bildirimi imkansız hale getirir.

Yeni numaralandırmalar "numaralandırma sınıfı" dır çünkü geleneksel numaralandırmaların (isim değerleri) yönlerini sınıfların yönleriyle (kapsam dahilindeki üyeler ve dönüşüm yokluğu) birleştirirler.

Yani, diğer kullanıcılar tarafından belirtildiği gibi, "güçlü numaralandırmalar" kodu daha güvenli hale getirir.

Bir "klasik" enumöğesinin temel türü, tüm değerlerine uyacak kadar büyük bir tamsayı türü olmalıdır enum; bu genellikle bir int. Ayrıca her numaralandırılmış tip aşağıdakilerle uyumlu olmalıdırchar işaretli / işaretsiz tam sayı tipiyle .

Bu, enumaltta yatan bir türün ne olması gerektiğinin geniş bir açıklamasıdır , bu nedenle her derleyici, klasikin altta yatan türü hakkında kendi başına kararlar alır enumve bazen sonuç şaşırtıcı olabilir.

Örneğin, bunun gibi bir sürü kez kod gördüm:

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

Yukarıdaki kod, bazı naif kodlayıcı derleyici saklayacak düşünerek olan E_MY_FAVOURITE_FRUITSişaretsiz 8bit türü içine değerleri ... ama bu konuda hiçbir garanti yoktur: derleyici tercih edebilir unsigned charya da intya da shortbu türden olan tüm sığdırmak için yeterince büyük olan, değerleri enum. Alanın eklenmesi E_MY_FAVOURITE_FRUITS_FORCE8bir yüktür ve derleyiciyi temel türü hakkında herhangi bir seçim yapmaya zorlamaz enum.

Tür boyutuna dayanan bir kod parçası varsa ve / veya E_MY_FAVOURITE_FRUITSbazı genişlikte olabileceğini varsayarsa (örneğin: serileştirme rutinleri) bu kod, derleyici düşüncelerine bağlı olarak bazı garip yollarla davranabilir.

Ve bazı iş arkadaşları dikkatsizce yeni bir değer katıyorsa, işleri daha da kötüleştirmek için enum:

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

Derleyici bundan şikayetçi değil! Yalnızca türün tüm değerlerine uyacak şekilde yeniden boyutlandırır enum(derleyicinin mümkün olan en küçük türü kullandığını varsayarsak, yapamayacağımız bir varsayımdır). Bu basit ve dikkatsiz ekleme enum, incelikle ilgili kodu kırabilir.

C ++ 11 enumve enum class(teşekkürler rdb ) için temel türü belirtmek mümkün olduğundan, bu sorun düzgün bir şekilde ele alınır:

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

Bir alanın bu tür aralığın dışında bir ifadesi varsa temel türü belirtmek, derleyicinin altta yatan türü değiştirmek yerine şikayet edecektir.

Bunun iyi bir güvenlik iyileştirmesi olduğunu düşünüyorum.

Peki neden enum sınıfı düz enum'a göre tercih ediliyor? , scoped ( enum class) ve uncoped ( enum) numaralar için temel türü seçebilirsek, başka ne enum classdaha iyi bir seçim yapar ? :

  • Örtük olarak dönüştürmezler int.
  • Çevredeki ad alanını kirletmezler.
  • İleriye beyan edilebilirler.

1
Sanırım C ++ 11
Sagar Padhye

11
Üzgünüm, ama bu cevap yanlış. "enum class" ın tür belirtme yeteneği ile ilgisi yoktur. Bu hem normal numaralandırmalar hem de numaralandırma sınıfları için var olan bağımsız bir özelliktir.
rdb

14
Bu anlaşma: * Enum sınıfları C ++ 11 yeni bir özelliktir. * Yazılan numaralandırmalar C ++ 11'de yeni bir özelliktir. Bunlar C ++ 11'deki iki ayrı alakasız yeni özellik. Her ikisini de kullanabilirsiniz ya da ikisini birden kullanabilirsiniz.
rdb

2
Alex Allain'in [ cprogramming.com/c++11/… adresinde bu blogda gördüğüm en eksiksiz basit açıklamayı sağladığını düşünüyorum . Geleneksel enum , tamsayı değerleri yerine adları kullanmak ve iyi bir şey olan önişlemci #defines kullanmaktan kaçınmak için iyiydi - netlik ekledi. enum sınıf numaralayıcısı sayısal bir değer kavramını kaldırır ve tanıtır kapsamı ve artar güçlü yazarak (iyi, olabilir artırmak :-) programı doğruluğunu. Sizi nesne odaklı düşünmeye bir adım daha yaklaştırır.
Jon Spencer

2
Bir yana, kodu incelerken her zaman eğlenceli olur ve aniden One Piece olur.
Justin Time - Monica'yı

47

Enum sınıfını normal numaralandırmalara göre kullanmanın temel avantajı, 2 farklı numaralandırma için aynı numaralandırma değişkenlerine sahip olabilmeniz ve yine de bunları çözebilmenizdir ( OP tarafından güvenli tip olarak belirtilmiştir )

Örneğin:

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

Temel numaralandırmalara gelince, derleyici redtipe Color1veya Color2aşağıdaki ifadede olduğu gibi olup olmadığını ayırt edemeyecektir .

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 Sorunuzu doğru okumadım. Bilmeyenler için bir eklenti olarak düşünün.
Saksham

tamam! Neredeyse unutuyordum
Oleksiy

Tabii ki, enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }ad alanı sorunlarını kolayca giderirsiniz. İsim alanı argümanı, burada bahsetmediğim üçünden biri.
Jo So

2
@Jo So Bu çözüm gereksiz bir çözümdür. Enum:, enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }Enum sınıfı: ile karşılaştırılabilir enum class Color1 { RED, GREEN, BLUE }. Erişim benzerdir: COLOR1_REDvs Color1::RED, ancak Enum sürümü her değerde "COLOR1" yazmanızı gerektirir; bu da bir enum sınıfının ad alanı davranışının önlediği yazım hataları için daha fazla alan sağlar.
cdgraham

2
Lütfen yapıcı eleştiri kullanın . Yazım hataları için daha fazla yer söylediğimde enum Color1, bir derleyicinin yakalayamayacağı değerleri başlangıçta tanımladığınız zaman, muhtemelen 'geçerli' bir isim olacağı anlamına gelir. Ben yazarsam RED, GREENve bu yüzden hiç çözemezse daha bir enum sınıfını kullanma konusunda enum Bananabelirttiğiniz gerektirdiğinden Color1::REDdeğeri (ad argümanı) erişmek için. Kullanmak için hala iyi zamanlar var enum, ancak bir 'nin ad alanı davranışı enum classgenellikle çok yararlı olabilir.
cdgraham

20

Numaralandırmalar bir tamsayı değer kümesini temsil etmek için kullanılır.

Ardından classanahtar sözcük enum, numaralandırmanın güçlü bir şekilde yazıldığını ve numaralandırıcılarının kapsamlandığını belirtir. Bu şekilde enumsınıflar sabitlerin kazara yanlış kullanımını önler.

Örneğin:

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

Burada Hayvan ve Evcil Hayvan değerlerini karıştıramayız.

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

7

C ++ 11 SSS aşağıdaki noktalardan bahseder:

konvansiyonel numaralandırmalar dolaylı olarak int'e dönüştürülür ve bir numaralandırma bir tamsayı gibi davranmak istemediğinde hatalara neden olur.

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

konvansiyonel numaralandırmalar numaralandırıcılarını çevre kapsamına ihraç ederek isim çatışmalarına neden olmaktadır.

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

Temel numaralandırma türü belirtilemez, karışıklığa, uyumluluk sorunlarına neden olur ve ileri bildirimi imkansız hale getirir.

// 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 "sınıf dışı" numaralandırmaların da yazılmasına izin verir . Ad alanı kirliliği sorunları, vb. Hala mevcuttur. Bundan daha uzun bir süre önce var olan ilgili cevaplara bir göz atın ..
user2864740

7
  1. dolaylı olarak int'e dönüştürme
  2. hangi tip altlık seçebilir
  3. Kirlenmeyi önlemek için ENUM ad alanı
  4. Normal sınıf ile karşılaştırıldığında, ileri beyan edilebilir, ancak yöntem yok

2

Bu diğer cevapların üstünde, C ++ 20'nin sahip olduğu sorunlardan birini çözdüğünü belirtmek gerekir enum class: ayrıntı düzeyi. Farazi hayal enum class, Color.

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

Bu, enumisimlerin global kapsamda olduğu ve bu nedenle ön ekin eklenmesi gerekmediği düz varyasyonla karşılaştırıldığında ayrıntılıdır Color::.

Bununla birlikte, C ++ 20'de using enumbir enumdaki tüm isimleri mevcut kapsama tanıtmak ve sorunu çözmek için kullanabiliriz.

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

Yani şimdi, kullanmamak için hiçbir neden yok enum class.


1

Diğer yanıtlarda belirtildiği gibi, sınıf enum int / bool'a dolaylı olarak dönüştürülemediğinden, buggy kodundan kaçınmaya da yardımcı olur:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

2
Önceki yorumumu tamamlamak için, gcc'nin artık -Wol-in-bool-bağlam adlı bir uyarısı olduğunu ve bu tür hataları yakalayacağını unutmayın.
Arnaud

0

Açıkça belirtilmemiş bir şey - kapsam özelliği size bir numaralandırma ve sınıf yöntemi için aynı ada sahip olma seçeneği sunar. Örneğin:

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
};
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.