C ++ 'da enums bayrak olarak nasıl kullanılır?


188

enumBayrak gibi davranmak C # [Flags]özelliğiyle güzel çalışır , ancak bunu C ++ ile yapmanın en iyi yolu nedir?

Örneğin, şunu yazmak istiyorum:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Ancak, int/ derlemeleri ile ilgili derleyici hataları alıyorum enum. Bunu künt dökümden daha iyi ifade etmenin bir yolu var mı? Tercihen, boost veya Qt gibi 3. taraf kütüphanelerden gelen yapılara güvenmek istemiyorum.

DÜZENLEME: Yanıtlarda belirtildiği gibi, derleyerek hata seahawk.flagsolarak önleyebilirsiniz int. Bununla birlikte, tip güvenliği uygulamak için bazı mekanizmalara sahip olmak istiyorum, bu yüzden birisi yazamaz seahawk.flags = HasMaximizeButton.


Visual C ++ 2013'te bildiğim kadarıyla bu [Flags]özellik gayet iyi çalışıyor:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, Hayır C ++ (2015 de) ile çalışmaz. Şunu mu demek istedin C #?
Ajay

5
@rivanov, [Flags] özniteliği yalnızca C ++ CLI'deki .Net Framework ile çalışır, yerel C ++ bu öznitelikleri desteklemez.
Zoltan Tirinda

Yanıtlar:


253

"Doğru" yol, numaralandırma için bit işleçlerini şu şekilde tanımlamaktır:

enum AnimalFlags
{
    HasClaws   = 1,
    CanFly     = 2,
    EatsFish   = 4,
    Endangered = 8
};

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

Diğer bit operatörleri vb. Numaralandırma aralığı int aralığını aşıyorsa gerektiği şekilde değiştirin.


42
^ bu. Tek soru, operatör tanımlarının nasıl otomatikleştirileceği / ayarlanacağıdır, böylece her yeni numara eklediğinizde bunları sürekli olarak tanımlamanız gerekmez.
eodabash

10
Ayrıca, int değeri enum tanımlayıcılarından herhangi birine karşılık gelmese bile, rasgele int'den enum tipine kadar döküm geçerli midir?
Ingo Schalk-Schupp

8
Bu tam bir saçmalık. Hangi üyesi AnimalFlagsifadeyle temsil edilir HasClaws | CanFly? Bu değil neyi enumler içindir. Tamsayıları ve sabitleri kullanın.
Yörüngedeki Hafiflik Yarışları

26
@LightnessRacesinOrbit: Bu doğru değil. Bir numaralandırma türünün etki alanı temel türünün etki alanıdır - yalnızca belirli olanlara bir ad verilmiştir. Ve sorunuzu cevaplamak için: " (HasClaws | CanFly)" üyesi .
Xeo

5
@MarcusJ: değerlerinizi 2 gücüyle sınırlamak, numaralarınızı bit bayrakları olarak kullanmanızı sağlar. Böylece 3 alırsanız, hem HasClaws(= 1) hem de (= 2) olduğunu bilirsiniz CanFly. Bunun yerine sadece düz aracılığıyla 4'e kadar olan değerler 1 atamak ve bir 3 alırsanız, tek olabilir EatsFishveya yeniden bir arada HasClawsve CanFly. Numaralandırmanız yalnızca özel durumları gösteriyorsa, ardışık değerler iyidir, ancak bayrakların birleşimi değerlerin bite özel olmasını gerektirir.
Christian Severin

122

Not (ayrıca biraz konu dışı): Benzersiz bayraklar yapmanın başka bir yolu da bir bit kaydırması kullanılarak yapılabilir. Ben, bunu okumayı daha kolay buluyorum.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3, // binary 1000
};

Bir int değerine kadar değerleri tutabilir, yani çoğu zaman vardiya miktarına açıkça yansıyan 32 bayraktır.


2
Kodu kopyalayıp yapıştırmayı kolaylaştırmak için lütfen son virgül (3,) 'ü silip iki nokta üst üste} ekleyin. Teşekkürler
Katu

4
Onaltılıktan bahsedilmiyor mu? Küfür!
Pharap

1
@Jamie, kardinaller her zaman 1 ile başlar, konuştuğunuz kişiye bağlı olarak yalnızca ordinaller 0 veya 1 ile başlayabilir.
Michael

2
@Michael, bu doğru! Bir numarada, genellikle BLAH_NONE için 0 ayırırsınız. :-) O hafızayı karıştırdığın için teşekkürler!
Jamie

1
@Katu • Son numaralandırmadaki gereksiz virgül standart tarafından izin verilir. Hoşuma gitmedi, ama Stroustrup'un bana ne söyleyeceğini zaten biliyorum ... "Sevmiyor musun? Kendi dilinizi yaratmaktan çekinmeyin. Ben de yaptım."
Eljay

55

Benim gibi tembel insanlar için, kopyalamak ve yapıştırmak için şablonlu çözüm:

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }

23
+1 Tembellik bir programcının üç büyük erdeminden
biridir

10
Bu çok güzel bir çözüm, sadece herhangi bir tür için nadiren bitsel işlemler sağlayacağına dikkat edin. Ben benzer bir şey kullanıyorum, ama ben biraz enable_if büyü ile birlikte uygulamak istediğiniz türleri tanımlayan özellikleri eklenmesi ile.
Rai

@Rai: her zaman bir ad alanına koyabilirsiniz ve usinguygun olduğu yerde olduğu gibi rel_ops.
Yakov Galka

1
@ybungalobill, ancak yine de kullanım kapsamındaki herhangi bir tür için geçerli olan işlemlerle aynı sorunu yaşayacaksınız, ki bu muhtemelen enum ile eşleşir? Bence özellikler büyük olasılıkla gerekli.
Rai

19
Bu kodu kullanmayın. Herhangi bir sınıfın yanlışlıkla işletilmesi için kapıyı açar. Ayrıca kod GCC katı derleme shitalshah.com/p/… geçmeyecek eski stil döküm kullanıyor .
Shital Shah

44

Windows ortamında çalışıyorsanız DEFINE_ENUM_FLAG_OPERATORS, winnt.h dosyasında sizin için işi yapan bir makro olduğunu unutmayın. Yani bu durumda, bunu yapabilirsiniz:

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)

seahawk.flags = CanFly | EatsFish | Endangered;

44

Seahawk.flags değişkeni nedir?

Standart C ++ 'da numaralandırmalar güvenli değildir. Etkili tamsayılardır.

AnimalFlags değişkeninizin tipi OLMAMALIDIR. Değişkeniniz int olmalı ve hata ortadan kalkacaktır.

Başkalarının önerdiği gibi onaltılık değerler koymak gerekli değildir. Fark yaratmıyor.

Enum değerleri varsayılan olarak int türünde ARE'dir. Böylece kesinlikle bitwise VEYA onları birleştirip bir araya getirebilir ve sonucu bir int'de saklayabilirsiniz.

Enum türü, değeri numaralandırılmış değerlerinden biri olan int'ün sınırlı bir alt kümesidir. Bu nedenle, bu aralığın dışında yeni bir değer yaptığınızda, numaralandırma türünüzün bir değişkenine döküm yapmadan bu değeri atayamazsınız.

İsterseniz enum değeri türlerini de değiştirebilirsiniz, ancak bu sorunun bir anlamı yoktur.

EDIT: Poster, tip güvenliği ile ilgilendiklerini ve int tipinde bulunmaması gereken bir değer istemediklerini söyledi.

Ancak AnimalFlags'ın değişken tipindeki AnimalFlags aralığına bir değer koymak güvenli değildir.

İnt türünün dışında olsa da aralık dışı değerleri kontrol etmenin güvenli bir yolu vardır ...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

enum AnimalFlags {
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8,

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Yukarıdakiler, 1,2,4 veya 8 değerine sahip farklı bir numaradan geçersiz bir bayrak koymanızı engellemez.

Mutlak tip güvenlik istiyorsanız, bir std :: set oluşturabilir ve her bayrağı orada saklayabilirsiniz. Yerden tasarruflu değildir, ancak tip güvenlidir ve size bir bitflag int ile aynı yeteneği verir.

C ++ 0x not: Güçlü yazılan numaralandırmalar

C ++ 0x sonunda nihayet tip güvenli numaralandırma değerleri olabilir ....

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error

4
Enum değerleri tamsayı değildir, ancak kolayca tamsayılara dönüşürler. Tipi HasClaws | CanFlybazı tamsayı tipi, ancak türüdür HasClawsIS AnimalFlagsdeğil bir tamsayı türü.
Karu

1
Ah, ama enumun doğru aralığını sadece bireysel bayrak değerleri değil, aynı zamanda bitsel kombinasyonları olarak tanımlarsak. O zaman eidolon'un cevabı doğrudur ve bu tip olarak sadece doğru bayrak numaralandırması kombinasyonlarının geçirilebileceğini savunur.
Scott

3
@Scott: C ++ standardının bir enum örneğinin geçerli değer aralığını bu şekilde tanımladığını belirtmek gerekir. "emin'in en küçük numaralandırıcı ve emaxın en büyük olduğu bir numaralandırma için, numaralandırmanın değerleri bmin - bmax aralığındaki değerlerdir, şöyle tanımlanır: K ikisinin tamamlayıcı temsili için 1, diğerleri için 0 olsun tamamlayıcı veya işaret-büyüklüğü gösterimi. bmax daha küçük değeri büyük olduğu ya da eşit max(|emin| − K, |emax|)ve eşit (1u<<M) - 1, burada Mnegatif olmayan bir tamsayıdır."
Ben Voigt

(Benim gibi) sadece enum değerlerinin bitsel olarak manipüle edilmesine izin veren ve şablonlar ve tip dökümüyle çok çirkin görünmeyen pratik bir şey isteyenler için bu iyi bir çözümdür; sadece yazılacak değişkenleri tanımlayın int.
Eric Sokolowsky

C ++, düzenli olduğunu da not enumteknik varsayılan olarak değil intaltta yatan türü olarak (ya da ön-C ++ 11 (IIRC), veya post-C ++ altta yatan türü belirtilen 11), her ne kadar enum class yapar . O daha sadece daha büyük olduğunu Bunun yerine, bir şey yeterince büyük için altta yatan tipi varsayılan tek gerçek kuralmış ile, tüm enumerator'ler temsil etmek intaçıkça eğer ihtiyacı olmak. Temel olarak, altta yatan tür "ne işe yarayacaksa", ancak muhtemelen int numaralandırıcılar için çok büyük olmadıkça int"olarak belirtilir.
Justin Time - Monica'yı

26

Eidolon'un şu anda kabul edilen cevabını çok tehlikeli buluyorum . Derleyicinin eniyileştiricisi, numaralandırmadaki olası değerler hakkında varsayımlar yapabilir ve geçersiz değerlerle çöp geri alabilirsiniz. Ve genellikle kimse bayrak enumlarındaki tüm olası permütasyonları tanımlamak istemez.

Brian R. Bondy'nin aşağıda belirttiği gibi, C ++ 11 kullanıyorsanız (ki herkes bunu yapmalıdır, bu iyi) şimdi bunu daha kolay yapabilirsiniz enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Bu, numaralandırma için bir tür belirleyerek istikrarlı bir boyut ve değer aralığı sağlar, numaralandırmaların kullanarak vb. Alanlara otomatik olarak aşağı inmesini engeller ve operatörler için kodun satır içi ve dolayısıyla normal sayılar kadar hızlı olmasını sağlamak için enum classkullanır constexpr.

11 öncesi C ++ lehçesine sıkışmış insanlar için

C ++ 11'i desteklemeyen bir derleyici ile sıkışmış olsaydım, bir int-türünü, daha sonra yalnızca bitsel işleçlerin ve bu numaradan türlerin değerlerini ayarlamak için kullanılmasına izin veren bir sınıfa sarmakla giderdim:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Bunu normal bir enum + typedef gibi tanımlayabilirsiniz:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

Ve kullanım da benzerdir:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

Ayrıca enum foo : type, ikinci şablon parametresini kullanarak ikili kararlı numaralandırmalar (C ++ 11 gibi) için altta yatan türü geçersiz kılabilirsiniz typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

operator boolGeçersiz kılmaların explicitint dönüşümleriyle sonuçlanmasını önlemek için geçersiz kılma işlemini C ++ 11'in anahtar kelimesiyle işaretledim, çünkü bunlar, onları yazarken bayrakların 0 veya 1'e daralmasına neden olabilir. C ++ 11 kullanamıyorsanız, bu aşırı yükü dışarıda bırakın ve örnek kullanımdaki ilk koşulu olarak yeniden yazın (myFlags & EFlagTwo) == EFlagTwo.


Not olarak, başlangıçta std::underlying_typebelirli bir tür kodlaması yerine tanımlanmış örnek operatörün kullanılmasını veya temel alınan türün doğrudan yerine tür takma adı olarak sunulmasını ve kullanılmasını öneririm . Bu şekilde, altta yatan türdeki değişiklikler, manuel olarak yapmak yerine otomatik olarak yayılır.
Justin Time - Monica'yı

17

Standart kitaplık sınıfı bit kümesini kullanarak, burada gösterildiği gibi bunu yapmanın en kolay yolu .

C # özelliğini tür güvenli bir şekilde taklit etmek için, bit kümesinin etrafına şablon argümanını şablona bir tür parametresi olarak verilen bir numaralandırma ile değiştirerek bit kümesi etrafına yazmanız gerekir. Gibi bir şey:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;

4
Daha eksiksiz kod için buna bakın: codereview.stackexchange.com/questions/96146/…
Shah

11

Bence şu ana kadar cevapların hiçbiri ideal değil. İdeal olmak için çözüm bekliyorum:

  1. Destek ==, !=, =, &, &=, |, |=ve ~(yani geleneksel anlamda operatörler a & b)
  2. Güvenli yazın, yani değişmez değerler veya tamsayı türleri gibi numaralandırılmamış değerlerin atanmasına izin vermeyin (numaralandırılmış değerlerin bitsel kombinasyonları hariç) veya bir tamsayı tipine bir enum değişkeninin atanmasına izin vermeyin
  3. Gibi ifadelere izin ver if (a & b)...
  4. Kötü makrolar, uygulamaya özgü özellikler veya diğer kesmek gerektirmez

Şimdiye kadar çözümlerin çoğu 2 veya 3 numaralı noktalara düşüyor. WebDancer benim görüşüme göre kapanıyor ancak 3. noktada başarısız oluyor ve her numaralandırma için tekrarlanması gerekiyor.

Benim önerdiğim çözüm, WebDancer'ın 3. noktaya da hitap eden genelleştirilmiş bir sürümüdür:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

enum class AnimalFlags : uint8_t 
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Bu, gerekli operatörlerin aşırı yüklenmesine neden olur, ancak bunları numaralandırılmış türlerle sınırlamak için SFINAE kullanır. Kısacası, tüm operatörleri tanımlamamıştım, ancak farklı olan tek şey &. Operatörler şu anda küreseldir (yani tüm numaralandırılmış türler için geçerlidir), ancak aşırı yükleri bir ad alanına (yaptığım şey) yerleştirerek veya ek SFINAE koşulları ekleyerek (belki de belirli altta yatan türleri kullanarak veya özel olarak oluşturulmuş tür diğer adları kullanarak) azaltılabilir. ). underlying_type_tBir C ++ 14 özelliktir ama iyi desteklenecek gibi görünüyor ve bir basit olan C ++ 11 taklit etmek kolaydırtemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Önerilen çözümünüz harika çalışıyor olsa da, bayrak olarak ele alınması amaçlanmayan numaralandırmalar için de bu modeli tanıtır. Muhtemelen Microsoft'tan DEFINE_ENUM_FLAG_OPERATORS gibi (kötü) makroların kullanılmasının nedeni budur.
WebDancer

@WebDancer, elbette haklısın, ama sonra zaten cevabımda söyledim. Sorunu ele almanın iki yolunu da önerdim - bir ad alanına koymak veya daha kısıtlayıcı bir SFINAE koşulu kullanmak.
Trevor

Demek istediğim, gerçekten dar bir ad alanı (örn. Ad alanı AllMyFlagEnums) yapmadıkça veya bir şekilde kodun aklımda kırıldığı sadece birkaç tam numarayı seçen bir SFINAE koşulu yoksa. Bunu riske atmak yerine, yerine "enum" adını ve bazen de "kötü" makroları değiştirdiğim bir "metin şablonu" kopyalayıp yapıştırıyorum. Keşke daha iyi bir yol olsaydı.
WebDancer

İlk olarak, yalnızca kodunuzun başka bir yerinde durması amaçlanan şeylerden birini yapmanız gerektiğinde bir soruna neden olur, örneğin başka bir numaradan bir değişmez, tamsayı veya bir öğe atama. Aksi takdirde, değiştirilmiş numaralandırma normal bir numaralandırma gibi davranır, örneğin elemanların ikisinin gücü olması gerekmez ve atama, karşılaştırma ve bitsel işlemler normal şekilde çalışır. Gerçekten değişmez değerler atamanız veya numaralandırmalar karıştırmanız gerekiyorsa, yine de açık bir şekilde yayınlayabilirsiniz, ayrıca amacınız daha net olacaktır. Dolayısıyla, kapsamı azaltmaya gerek kalmayacağı ihtimali vardır.
Trevor

İkincisi, kapsamı azaltmanız gerekse bile, ad alanının dar olması gerekmeyebilir - ancak bu ne yaptığınıza bağlı olacaktır. Bir kitaplık üzerinde çalışıyorsanız, belki de zaten bir ad alanındaki numaralara bağlı olan kodunuz zaten vardır, o zaman numaralandırma kodu sadece aynı ad alanına gider. Bir sınıf için enum davranışına ihtiyacınız varsa (belki de enumları yöntem bağımsız değişkenleri veya sınıfın üye değişkenleri olarak kullanmak istiyorsanız), enum kodunu aynı etki için sınıfa yerleştirin. Alt satırda, bir ad alanını yalnızca numaralandırmaların etrafına sarmanıza gerek yoktur - ancak yapabilirsiniz.
Trevor

8

C ++ standardı bunun hakkında açıkça konuşuyor, bkz. Bölüm "17.5.2.1.3 Bitmask türleri":

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Bu "şablon" verildiğinde:

enum AnimalFlags : unsigned int
{
    HasClaws = 1,
    CanFly = 2,
    EatsFish = 4,
    Endangered = 8
};

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

Ve diğer operatörler için de benzer. Ayrıca "constexpr" 'e dikkat edin, derleyicinin derleyici işleçlerini derleyebilmesini istiyorsanız gereklidir.

C ++ / CLI kullanıyorsanız ve ref sınıflarının numaralarını atamak istiyorsanız bunun yerine izleme referanslarını kullanmanız gerekir:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

NOT: Bu örnek tam değildir, tam bir operatör kümesi için "17.5.2.1.3 Bitmask tipleri" bölümüne bakın.


6

Kendimi aynı soruyu sordum ve soruya benzer bir genel C ++ 11 tabanlı çözüm buldum:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

Arayüz zevkinize göre geliştirilebilir. Sonra şu şekilde kullanılabilir:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;

2
Daha iyi ve daha eksiksiz kod için buna bakın: codereview.stackexchange.com/questions/96146/…
Shah

5
Numeric_limits kullanmam dışında, kod neredeyse aynı. Sanırım bu tip güvenli bir enum sınıfına sahip olmanın yaygın bir yoludur. Numeric_limits kullanmanın, her numaralamanın sonuna bir SENTINEL koymaktan daha iyi olduğunu iddia ediyorum.
Omair

1
Bu çok büyük bir set.
Yörüngedeki Hafiflik Yarışları

(potansiyel olarak ...)
Yörüngedeki Hafiflik Yarışları

5

Derleyiciniz henüz güçlü biçimde yazılmış numaralandırmaları desteklemiyorsa , c ++ kaynağından aşağıdaki makaleye göz atabilirsiniz :

Özetden:

Bu makalede,
yalnızca güvenli ve meşru olanlara izin vermek ve tüm geçersiz bit manipülasyonlarını derleme zamanı hatalarına dönüştürmek için bit işlemlerini kısıtlama sorununa bir çözüm sunulmaktadır . Hepsinden iyisi, bit işlemlerinin sözdizimi değişmeden kalır ve muhtemelen henüz algılanmayan hataları düzeltmek dışında bitlerle çalışan kodun değiştirilmesi gerekmez.


5

Aşağıdaki makroyu kullanıyorum:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

Yukarıda belirtilenlere benzer, ancak birkaç iyileştirme vardır:

  • Tür güvenlidir (altta yatan türün bir olduğunu varsaymaz int)
  • Temel türü manuel olarak belirtmek gerekmez (@LunarEclipse'nin cevabının aksine)

Type_traits içermesi gerekir:

#include <type_traits>

4

C ++ 11 için şablon ve anahtar kelime eksikliği için C ++ 98 için kodunu düzeltmek ve Güvenli Bool deyimini kullanarak Uliwitness cevap üzerinde ayrıntılı olarak istiyorum .std::underlying_type<>explicit

Ayrıca, enum değerlerinin herhangi bir açık atama olmadan sıralı olabilmesi için değiştirdim, böylece

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Ardından ham bayraklar değerini

seahawk.flags.value();

İşte kod.

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};

3

Burada, bireysel numaralandırma değerleri için gerçekten bir kullanımınız yoksa (örneğin, bunları kapatmanız gerekmez) bitma maskeleri için bir seçenek ... ve ikili uyumluluğu korumak konusunda endişelenmiyorsanız, yani: bitlerinizin nerede yaşadığı umurumda değil ... muhtemelen sizsiniz. Ayrıca, kapsam belirleme ve erişim kontrolü ile fazla ilgilenmemeniz daha iyi olur. Hmmm, numaralandırmalar bit alanları için bazı güzel özelliklere sahip ... merak ediyorum kimse bunu denediniz mi :)

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Hayatın harika olduğunu görebiliriz, ayrık değerlerimiz var ve hoş bir int var & & | hala bitlerinin ne anlama geldiğinin bağlamına sahip olan kalpler içeriğimize. Win10 x64'te Güncelleme 3 ile Microsoft'un VC ++ derleyicisini kullanmaya devam ettiğim ve derleyici bayraklarına dokunmadığım sürece her şey tutarlı ve tahmin edilebilir ... benim için ...

Her şey harika olsa da ... şimdi bayrakların anlamıyla ilgili bir bağlamımız var , çünkü programınızın yapabileceğiniz tek bir ayrı görevden daha fazla sorumlu olabileceği korkunç gerçek dünyadaki bit alanında bir birliktelik var. hala yanlışlıkla (oldukça kolay) farklı sendikaların iki bayrak alanını birlikte parçalayın (her ikisi de ints oldukları için AnimalProperties ve ObjectProperties), tüm parçalarınızı karıştırın, bu da izini sürmek korkunç bir böcek ... ve nasıl bildiğimi Bu yazıdaki birçok insan bitmasklarla çok sık çalışmaz, çünkü onları oluşturmak kolaydır ve onları korumak zordur.

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Böylece, sendika beyanınızı "Bayraklar" a doğrudan erişimi önlemek için gizli hale getirirsiniz ve alıcılar / ayarlayıcılar ve operatör aşırı yüklemeleri eklemeniz gerekir, ardından tüm bunlar için bir makro oluşturursunuz ve temel olarak, denediğinizde başladığınız yere geri dönersiniz. bunu bir Enum ile yap.

Ne yazık ki kodunuzun taşınabilir olmasını istiyorsanız, A) bit düzenini garanti etmenin veya B) derleme zamanında bit düzenini belirlemenin (böylece onu izleyebilmeniz ve en azından sürümler / platformlar vb.) Bit alanlı bir yapıda ofset

Çalışma zamanında, alanları ayarlayarak hileler oynayabilir ve hangi bitlerin değiştiğini görmek için bayrakları XORing yapabilirsiniz,% 100 tutarlı, platformdan bağımsız ve tamamen deterministik bir çözüme sahip olan ayetler bana oldukça bok gibi geliyor: ENUM.

TL; DR: Nefret dinlemeyin. C ++ İngilizce değildir. C'den devralınan kısaltılmış bir anahtar kelimenin gerçek tanımının kullanımınıza uymaması , anahtar kelimenin C ve C ++ tanımı kesinlikle kullanım durumunuzu içerdiğinde kullanmamanız gerektiği anlamına gelmez . Yapıları yapı dışındaki yapıları modellemek için yapıları, okul ve sosyal kast dışındaki yapıları da sınıflamak için kullanabilirsiniz. Topraklanmış değerler için şamandıra kullanabilirsiniz. Char'ı yanmayan veya bir roman, oyun veya filmdeki bir kişi için kullanabilirsiniz. Dil spesifikasyonundan önce bir anahtar kelimenin anlamını belirlemek için sözlüğe giden herhangi bir programcı ... bir dilimi orada tutacağım.

Kodunuzun konuşulan dilden sonra modellenmesini istiyorsanız, en çok bitfields için enum'ları da kullanan Objective-C'de yazmaktan daha iyi olursunuz.


3

Sadece sözdizimsel şeker. Ek meta veri yok.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

İntegral tipteki bayrak operatörleri sadece çalışır.


IMHO bu en iyi cevap. Temiz ve basit, kolay istemci sözdizimi. Ben sadece "constexpr uint8_t" yerine "const int" kullanmak istiyorum, ama kavram aynı.
yoyo

(üzgünüm, "constexpr int")
yoyo

3

Şu anda enum bayrakları için dil desteği yoktur, Meta sınıfları c ++ standardının bir parçası olsa bile bu özelliği doğası gereği ekleyebilir.

Benim çözümüm, altta yatan türünü kullanarak numaralandırma sınıfı için tür güvenli bitsel işlemler için destek ekleyerek, yalnızca numaralandırılmış örnek şablon işlevleri oluşturmak olacaktır:

Dosya: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Kolaylık sağlamak ve hataları azaltmak için, numaralandırma işlemlerinizi numaralandırmalar ve tamsayılar için de sarmak isteyebilirsiniz:

Dosya: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Olası kullanım:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}

3

@Xaqq, burada bir flag_setsınıf tarafından numaralandırma bayraklarını kullanmak için gerçekten güzel bir tür güvenli yol sağladı .

Kodu GitHub'da yayınladım , kullanım aşağıdaki gibidir:

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}

2

Nesneleri ve nesne koleksiyonlarını karıştırıyorsunuz. Özellikle, ikili bayrakları ikili bayraklarla karıştırıyorsunuz. Uygun bir çözüm şöyle görünecektir:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};

2

İşte benim aşırı yükleme veya döküm demet gerek kalmadan benim çözüm:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Bence sorun yok, çünkü biz enumları ve ints zaten (güçlü yazılan) tanımlamak.

Tıpkı (daha uzun) bir yan not gibi,

  • güçlü yazılan numaralandırmalar kullanmak ve
  • bayraklarınızla biraz uğraşmanıza gerek yok
  • performans sorun değil

Ben bununla gelirdi:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

C ++ 11 başlatıcı listeleri ve kullanarak enum class.


Bu arada, enums bayrakları için hiç tavsiye etmem. Basit sebep: bayrak kombinasyonları tekrar numaralandırmanın öğeleri değildir. Yani bu oldukça uygun görünmüyor. Alternatif olarak using Flags = unsigned long, bayrak değerlerini /*static*/ const Flags XY = 0x01ve benzerlerini içeren bir ad alanı veya yapı içinde kullanırım .
yau

1

Yukarıdaki gibi (Kai) veya aşağıdakileri yapın. Gerçekten sıralamalar "Numaralandırma", yapmak istediğiniz bir set var, bu yüzden gerçekten stl :: set kullanmalısınız

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}

1

Belki de Objective-C'nin NS_OPTIONS'ı gibi.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.

Cevabınızın neden en uygun olduğunu açıklayabilir misiniz? Bu soruyu cevaplayan başka yanıtlar da var, bu yüzden lütfen yanıtınızı farklılaştırmak için bazı bilgiler ekleyin.
trevorp
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.