enum türleri için ad alanları - en iyi yöntemler


104

Çoğu zaman, birkaç numaralandırılmış türe birlikte ihtiyaç duyar. Bazen isim çatışması yaşanır. Bunun için iki çözüm akla geliyor: bir ad alanı veya 'daha büyük' ​​enum öğe adları kullanın. Yine de ad alanı çözümünün iki olası uygulaması vardır: iç içe enum içeren bir kukla sınıf veya tam gelişmiş bir ad alanı.

Üç yaklaşımın da artılarını ve eksilerini arıyorum.

Misal:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

19
Öncelikle Color :: Red, Feeling: Angry, vb.
Kullanırım

güzel soru, ad alanı yöntemini kullandım ....;)
MiniScalope

18
her şeyin üzerindeki "c" ön eki okunabilirliğe zarar verir.
Kullanıcı

8
@User: neden, Macar tartışmasını açtığınız için teşekkür ederiz :)
xtofl

5
Numaralamayı olduğu gibi adlandırmanıza gerek olmadığını unutmayın, numaralandırmalar enum e {...}anonim olabilir, yani enum {...}ad alanı veya sınıfa sarıldığında çok daha anlamlı olur.
kralyk

Yanıtlar:


73

Orijinal C ++ 03 yanıtı:

Yarar bir gelen namespace(üzerinde classkendinizin kullanabileceği) 'dir usingistediğiniz zaman bildirimleri.

A kullanmanın sorununamespace , ad alanlarının kodun başka bir yerinde genişletilebilmesidir. Büyük bir projede, iki farklı numaralandırmanın her ikisinin de çağrıldığını düşünmeyeceği garanti edilemez.eFeelings

Daha basit görünen kod için, structmuhtemelen içeriğin herkese açık olmasını istediğiniz için a kullanıyorum .

Bu uygulamalardan herhangi birini yapıyorsanız, eğrinin önündesiniz ve muhtemelen bunu daha fazla incelemeniz gerekmiyor.

Daha yeni, C ++ 11 tavsiyesi:

C ++ 11 veya sonraki bir sürümünü kullanıyorsanız enum class, numaralandırmanın adı içindeki enum değerlerini örtük olarak kapsayacaktır.

İle enum classsize belirsiz veya hatalı kod keşfetmenize yardımcı olabilir örtük dönüşüm ve tamsayı türlerine karşılaştırmalar, ama pratikte kaybedecektir.


4
Yapı fikrine katılıyorum. Ve iltifat için teşekkürler :)
xtofl

3
+1 C ++ 11 "enum sınıfı" sözdizimini hatırlayamadım. Bu özellik olmadan numaralandırmalar eksiktir.
Grault

"Enum sınıfının" örtük kapsamı için "kullanma" seçeneğini kullanma seçenekleridir. örneğin 'using Color :: e;' ekleyecektir. kodlamak için 'cRed' kullanımına izin verin ve bunun Color :: e :: cRed olması gerektiğini biliyor musunuz?
JVApen

20

Bilginize C ++ 0x'de, bahsettiğiniz gibi durumlar için yeni bir sözdizimi vardır (bkz. C ++ 0x wiki sayfası )

enum class eColors { ... };
enum class eFeelings { ... };

11

Önceki cevapları şöyle bir şeye hibritledim: (DÜZENLE: Bu yalnızca C ++ 11 öncesi için kullanışlıdır. C ++ 11 kullanıyorsanız, kullanın enum class)

Tüm proje numaralarımı içeren büyük bir başlık dosyam var, çünkü bu numaralandırmalar işçi sınıfları arasında paylaşılıyor ve numaralandırmaları işçi sınıflarının içine koymak mantıklı değil.

Bu struct, public: sözdizimsel şekerden kaçınır ve typedefbu numaralandırmaların değişkenlerini diğer işçi sınıfları içinde gerçekten bildirmenize izin verir.

Bir ad alanı kullanmanın hiç yardımcı olduğunu sanmıyorum. Belki bir C # programcı olduğum için bu, ve orada var değerlerini bahsederken ben buna alışığım bu yüzden, enum türü adı kullanmak.

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}

9

Bunun için bir sınıf kullanmaktan kesinlikle kaçınırım; bunun yerine bir ad alanı kullanın. Soru, bir ad alanı kullanıp kullanmamaya veya enum değerleri için benzersiz kimlikler kullanıp kullanmamaya bağlıdır. Şahsen, kimliklerimin daha kısa ve umarım daha açıklayıcı olması için bir ad alanı kullanırdım. Daha sonra uygulama kodu bir 'ad alanını kullanma' yönergesi kullanabilir ve her şeyi daha okunaklı hale getirebilir.

Yukarıdaki örneğinizden:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

Neden bir sınıfa göre bir ad alanını tercih edeceğinize dair bir ipucu verebilir misiniz ?
xtofl

@xtofl: 'Sınıf Renklerini kullanarak' yazamazsınız
MSalters

2
@MSalters: Ayrıca yazamazsınız Colors someColor = Red;çünkü ad alanı bir tür oluşturmaz. Bunun Colors::e someColor = Red;yerine yazmak zorundasın ki bu oldukça mantıksız.
SasQ

@SasQ misiniz Kullanmak zorunda değildir Colors::e someColorbir ile bile struct/classbir kullanmak istiyorsa switchaçıklamada? Bir anonim kullanırsanız enum, anahtar bir struct.
Macbeth's Enigma

1
Üzgünüm, ama const e cbana pek okunamaz görünüyor :-) Bunu yapma. Ancak bir ad alanı kullanmak iyidir.
dhaumann

7

Bir sınıf veya bir ad alanı kullanmak arasındaki fark, sınıfın bir ad alanı gibi yeniden açılamamasıdır. Bu, ad alanının gelecekte kötüye kullanılması olasılığını ortadan kaldırır, ancak aynı zamanda numaralandırma kümesine ekleyemeyeceğiniz bir sorun da vardır.

Bir sınıfı kullanmanın olası bir yararı, ad alanları için geçerli olmayan şablon türü argümanları olarak kullanılabilmeleridir:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

Kişisel olarak, kullanmanın hayranı değilim ve tam nitelikli isimleri tercih ediyorum, bu yüzden bunu isim alanları için bir artı olarak görmüyorum. Ancak, bu muhtemelen projenizde vereceğiniz en önemli karar değil!


Sınıfların yeniden açılmaması da potansiyel bir dezavantajdır. Renk listesi de sonlu değildir.
MSalters

1
Bir sınıfı yenilememenin potansiyel bir avantaj olduğunu düşünüyorum. Daha fazla renk istersem, sınıfı daha fazla renkle yeniden derlerim. Bunu yapamazsam (kod bende olmadığını söyleyin), o zaman hiçbir durumda ona dokunmak istemiyorum.
Thomas Eding

@MSalters: Bir sınıfı yeniden açma olanağının olmaması sadece bir dezavantaj değil, aynı zamanda bir güvenlik aracıdır. Çünkü bir sınıfı yeniden açmak ve numaraya bazı değerler eklemek mümkün olduğunda, bu numaralandırmaya zaten bağlı olan ve yalnızca eski değerler kümesini bilen diğer kitaplık kodunu kırabilir. Daha sonra bu yeni değerleri mutlu bir şekilde kabul eder, ancak çalışma zamanında onlarla ne yapacağını bilmemekten kurtulur. Açık-Kapalı Prensibi Unutmayın: Sınıf, değişiklik için kapatılmalı , ancak genişletmek için açık olmalıdır . Genişletmekle, mevcut koda eklemeyi değil, onu yeni kodla sarmayı (ör. Türetmeyi) kastediyorum.
SasQ

Bu nedenle, numaralandırmayı genişletmek istediğinizde, onu ilkinden türetilmiş yeni bir tür yapmalısınız (eğer C ++ ...; / 'da kolayca mümkün olsaydı). Daha sonra, bu yeni değerleri anlayan yeni kod tarafından güvenle kullanılabilir, ancak eski kod tarafından yalnızca eski değerler kabul edilir (aşağı dönüştürülerek). Bu yeni değerlerden herhangi birini yanlış türde (genişletilmiş olan) olarak kabul etmemelidirler. Yalnızca anladıkları eski değerler doğru (temel) türde kabul edilir (ve yanlışlıkla yeni türden de olabilir, bu nedenle yeni kodla da kabul edilebilir).
SasQ

7

Bir sınıf kullanmanın avantajı, üzerine tam teşekküllü bir sınıf oluşturabilmenizdir.

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

Yukarıdaki örnekte gösterildiği gibi, bir sınıf kullanarak şunları yapabilirsiniz:

  1. C ++ 'ın geçersiz değerden bir dönüşüm yapılmasına izin vermesini yasaklayın (ne yazık ki derleme zamanı değil),
  2. yeni oluşturulan numaralandırmalar için bir (sıfır olmayan) varsayılan ayarlayın,
  3. Bir seçimin dize gösterimini döndürmek gibi başka yöntemler ekleyin.

operator enum_type()C ++ 'nın sınıfınızı temel numaralandırmaya nasıl dönüştüreceğinizi bilmesi için bildirmeniz gerektiğini unutmayın . Aksi takdirde, türü bir switchifadeye geçiremezsiniz .


Bu çözüm bir şekilde burada gösterilenle alakalı mı ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Her seferinde bu kalıbı yeniden yazmak zorunda olmadığım bir şablon haline nasıl getireceğimi düşünüyorum Onu kullanmam lazım
SasQ

@SasQ: Benzer görünüyor, evet. Muhtemelen aynı fikir. Ancak, oraya çok sayıda 'ortak' yöntem eklemediğiniz sürece bir şablonun yararlı olup olmadığından emin değilim.
Michał Górny

1. Pek doğru değil. Enum'un geçerli olup olmadığını, const_expr yoluyla veya bir int için özel kurucu aracılığıyla derleme zamanını kontrol edebilirsiniz.
xryl669

5

Numaralandırmalar kapsama alanlarına göre ayarlandığından , genel ad alanını kirletmekten kaçınmak ve ad çakışmalarını önlemeye yardımcı olmak için bunları bir şeye sarmak muhtemelen en iyisidir . Ben sınıfa bir ad tercih çünkü namespacetutma torbası gibi hissediyor, oysa classsağlam bir nesne gibi hissediyor (Bkz structvs classtartışma). Bir ad alanının olası bir yararı, daha sonra genişletilebilmesidir - değiştiremeyeceğiniz üçüncü taraf koduyla uğraşıyorsanız kullanışlıdır.

C ++ 0x ile enum sınıfları aldığımızda elbette bu tartışmalı bir konudur.


numaralandırma sınıfları ... buna bakmanız gerekiyor!
xtofl

3

Ayrıca numaralarımı derslere sarma eğilimindeyim.

Richard Corden'in işaret ettiği gibi, bir sınıfın yararı, c ++ anlamında bir tür olması ve böylece onu şablonlarla kullanabilmenizdir.

İhtiyaçlarım için özel toolbox :: Enum sınıfım var, temel işlevleri sağlayan her şablon için uzmanlaştım (esas olarak: bir enum değerini bir std :: string'e eşleyerek G / Ç'nin daha kolay okunması için).

Benim küçük şablonum, izin verilen değerleri gerçekten kontrol etme avantajına da sahiptir. Derleyici, değerin gerçekten enum'da olup olmadığını kontrol etmekte biraz gevşek:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

Hiçbir anlamı olmayan (ve beklemeyeceğiniz) bir enum değeriyle kaldığınız için derleyicinin bunu yakalayamaması beni her zaman rahatsız etti.

Benzer şekilde:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Bir kez daha main bir hata döndürür.

Sorun, derleyicinin mevcut en küçük gösterime sığmasıdır (burada 2 bite ihtiyacımız var) ve bu gösterime uyan her şeyin geçerli bir değer olarak kabul edilmesi.

Bazen bir anahtar yerine olası değerler üzerinde bir döngüye sahip olmayı tercih edeceğiniz bir sorun da vardır, böylece numaralandırmaya her değer eklediğinizde tüm anahtarlarınızı değiştirmek zorunda kalmazsınız.

Sonuçta benim küçük yardımcım, numaralandırmalarım için işleri gerçekten kolaylaştırıyor (elbette, bazı ek yükler ekler) ve bu yalnızca, her numaralamayı kendi yapısına yerleştirdiğim için mümkündür :)


4
İlginç. Enum sınıfınızın tanımını paylaşır mısınız?
momeara
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.