it-swarm.dev

C++ 'da Hangi Typesafe Enum Kullanıyorsunuz?

C++ 'da yerleşik numaralandırmaların typeafe olmadığı yaygın bir bilgidir. Hangi sınıfları emniyetli emzik uygulayan sınıfların orada kullanıldığını merak ediyordum ... Kendim şu "bisikleti" kullanıyorum, ancak biraz ayrıntılı ve sınırlı:

typesafeenum.h:

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

typesafeenum.cpp:

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

Kullanımı:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

Ek: Sanırım gereksinimler hakkında daha spesifik olmalıydım. Onları özetlemeye çalışacağım:

Öncelik 1: Bir enum değişkenini geçersiz bir değere ayarlamak, istisnasız imkansız olmalıdır (bir derleme zamanı hatası).

Öncelik 2: Bir enum değerini bir int'den/int'ye dönüştürmek tek bir açık işlev/yöntem çağrısı ile mümkün olmalıdır.

Öncelik 3: Kompakt, zarif ve uygun beyan ve mümkün olduğunca kullanımı

Öncelik 4: Enum değerlerini dizgelere ve dizgelere dönüştürme.

Öncelik 5: (Sahip olmak güzel) Enum değerlerini yineleme imkanı.

44
Alex Jenter

Şu anda Boost Vault (dosya adı enum_rev4.6.Zip) 'dan Boost.Enum teklifi ile oynuyorum. Asla resmen Boost'a dahil edilmek üzere sunulmamasına rağmen, olduğu gibi kullanılabilir. (Belgeler eksik, ancak açık kaynak kodları ve iyi testler ile yapıldı.)

Boost.Enum, böyle bir enum bildirmenizi sağlar:

BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

Ve otomatik olarak buna genişletmek var:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};

Listelediğiniz önceliklerin beşini de yerine getirir.

42
Josh Kelley

Güzel bir uzlaşma yöntemi şudur:

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

Versiyonunuzun olduğu gibi yazı tipi güvenliği değildir, ancak kullanımı standart numaralardan daha iyidir ve ihtiyacınız olduğunda tamsayı dönüşümünün avantajlarından yararlanmaya devam edebilirsiniz.

19
Charlie

C++ 0x typesafe enums kullanıyorum. String işlevinden/dizge işlevini sağlayan bazı yardımcı şablonlar/makrolar kullanıyorum.

enum class Result { Ok, Cancel};
12
Roddy

Yapmıyorum. Küçük bir fayda için çok fazla ek yükü. Ayrıca, seri hale getirme için farklı veri türlerine numaralandırma yapabilmek çok kullanışlı bir araçtır. "Güvenli bir numaralandırma" numaralandırmasının, C++ 'nın zaten yeterince iyi bir uygulama sunacağı genel giderlere ve karmaşıklığa değeceği bir örnek görmedim.

6
nlaq

Kişisel olarak typesafe enum idiom uyarlanmış bir sürümünü kullanıyorum. Düzenlemenizde belirttiğiniz beş "gereksinimi" tamamlamıyor, ancak yine de bazılarına kesinlikle katılmıyorum. Örneğin, Prio # 4'ün (değerlerin dizgelere dönüştürülmesi) tür güvenliği ile nasıl bir ilgisi olduğunu anlamıyorum. Bireysel değerlerin zaman dizesi gösteriminin çoğu, yine de türün tanımından ayrı olmalıdır (neden basit bir nedenden dolayı i18n'yi düşünün). Prio # 5 (isteğe bağlı olan iteratio) görmek istediğim en güzel şeylerden biri naturally enums'de oluyor, bu yüzden isteğinizde "isteğe bağlı" olarak göründüğü için üzüldüm, ama sanki beginname __/endişlevleri veya bir enum_iterator gibi ayrı bir yineleme sistemi ile ya da STL ve C++ 11 foreach ile sorunsuz çalışmasını sağlayan bir enum_iterator aracılığıyla daha iyi ele alınmış gibi görünüyor.

OTOH bu basit deyim, Prio # 3 Prio # 1'i, çoğunlukla sadece daha fazla tip bilgisine sahip enumname__'leri silmesinden dolayı güzel bir şekilde sunar. Bahsetmiyorum bile, çoğu zaman herhangi bir dış bağımlılık başlığı gerektirmeyen çok basit bir çözümdür, bu yüzden taşıması oldukça kolaydır. Ayrıca, a-la-C++ 11 kapsamına giren numaralandırma yapma avantajına sahiptir:

// This doesn't compile, and if it did it wouldn't work anyway
enum colors { salmon, .... };
enum fishes { salmon, .... };

// This, however, works seamlessly.
struct colors_def { enum type { salmon, .... }; };
struct fishes_def { enum type { salmon, .... }; };

typedef typesafe_enum<colors_def> colors;
typedef typesafe_enum<fishes_def> fishes;

Çözümün sağladığı tek "delik", farklı türlerdeki (veya bir enumve int) farklı türlerdeki (veya bir enumve int) doğrudan karşılaştırılmasını engellememesi gerçeğini ele almamasıdır; int'e dönüşüm:

if (colors::salmon == fishes::salmon) { .../* Ooops! */... }

Ancak şu ana kadar böyle sorunların sadece derleyiciyle daha iyi bir karşılaştırma önererek çözülebileceğini gördüm - örneğin, açıkça herhangi bir iki farklı enumtürünü karşılaştıran bir işleç sağlayarak, sonra başarısızlığa zorlayarak:

// I'm using backports of C++11 utilities like static_assert and enable_if
template <typename Enum1, typename Enum2>
typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool >
::type operator== (Enum1, Enum2) {
    static_assert (false, "Comparing enumerations of different types!");
}

Şimdiye kadar kodları kırmış gibi görünmese de, belirli bir sorunu başka bir şey yapmadan açıkça ele alsa da, böyle bir şeyin bir şey olduğundan emin değilim " should " do ( Daha önce başka yerde ilan edilen dönüşüm operatörleri arasında yer alan enumname__s ile etkileşime gireceğinden şüpheleniyorum, memnuniyetle bu konuda yorum alırım). 

Bunu yukarıdaki yazı tipi deyimiyle birleştirmek, çok karanlık bir şey yapmak zorunda kalmadan insanlıkta (okunabilirlik ve bakım kolaylığı) C++ 11 enum class'ye nispeten yakın bir şey verir. Ve bunun eğlenceli olduğunu itiraf etmeliyim ki, aslında ask derleyiciyi enumname__s ile ilgileniyor olsaydım ...

2
Luis Machuca

Benim düşüncem, bir problem yaratıp ardından bir çözüme ulaştırmaktır. Değerlerin bir numaralandırılması için ayrıntılı bir çerçeve yapmaya gerek olmadığını görüyorum. Eğer size adanmışsanız değerlerinizi sadece belirli bir grubun üyeleri olarak görüyorsanız, benzersiz bir set veri tipinin bir türevini kesebilirsiniz.

2
Paul Nathan

Buna burada 'ya farklı bir konuda cevap verdim. Orijinal enum tanımında değişiklik yapılmasını gerektirmeden (ve dolayısıyla enum tanımlanmadığınız durumlarda kullanıma izin vermeden) aynı işlevselliğin çoğuna izin veren farklı bir yaklaşım tarzıdır. Aynı zamanda çalışma zamanı aralık kontrolünü sağlar.

Yaklaşımımın dezavantajı, enum ve yardımcı sınıf arasındaki eşleşmeyi programlı olarak zorlamamasıdır, bu yüzden paralel olarak güncellenmeleri gerekir. Benim için çalışıyor ama YMMV.

1
Nick

Bence Java enum, takip etmek için iyi bir model olur. Temelde, Java formu şöyle görünür:

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Java yaklaşımıyla ilgili ilginç olan şey OK ve CANCEL öğelerinin değişmez olması, Result 'ın tekil örnekleri (gördüğünüz yöntemlerle). Result öğesinin başka örneklerini oluşturamazsınız. Tekil olduklarından, işaretçiyi/referansı --- --- çok kullanışlı bir şekilde karşılaştırabilirsiniz. :-)

ETA: Java'da, bit maskelerini elle yapmak yerine, bit kümesini belirtmek için EnumSet işlevini kullanırsınız (Set arabirimini uygular ve setler gibi çalışır; ancak bit maskeleri kullanılarak uygulanır). Elle yazılmış bitmask manipülasyonundan çok daha okunaklı!

1

boost::variant kullanın!

Yukarıdaki fikirlerin çoğunu denedikten ve onları eksik bulduktan sonra bu basit yaklaşıma değindim:

#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> BC;

void ab(const AB & e)
{
  if(isA(e))
    std::cerr << "A!" << std::endl;
  if(isB(e))
    std::cerr << "B!" << std::endl;
  // ERROR:
  // if(isC(e))
  //   std::cerr << "C!" << std::endl;

  // ERROR:
  // if(e == 0)
  //   std::cerr << "B!" << std::endl;
}

void bc(const BC & e)
{
  // ERROR:
  // if(isA(e))
  //   std::cerr << "A!" << std::endl;

  if(isB(e))
    std::cerr << "B!" << std::endl;
  if(isC(e))
    std::cerr << "C!" << std::endl;
}

int main() {
  AB a;
  a = A;
  AB b;
  b = B;
  ab(a);
  ab(b);
  ab(A);
  ab(B);
  // ab(C); // ERROR
  // bc(A); // ERROR
  bc(B);
  bc(C);
}

Muhtemelen, kazan plakasını oluşturmak için bir makro ile gelebilir. (Yaparsan bana haber ver.)

Diğer yaklaşımlardan farklı olarak, bu aslında güvenlidir ve eski C++ ile çalışır. Örneğin, A, B, bir tamsayı ya da neredeyse Haskell98 tipi güvenlik seviyesine sahip hiçbir şey olmayan bir değeri temsil etmek için, boost::variant<int, A_t, B_t, boost::none> gibi havalı tipler bile yapabilirsiniz.

Farkında olmak için olumsuz taraflar:

  • en azından eski destekle - 1.33 destekle bir sistemdeyim - varyantınızdaki 20 maddeyle sınırlısınız; Bununla birlikte bir çalışma var
  • derleme süresini etkiler
  • çılgınca hata mesajları - ama bu sizin için C++

Güncelleştirme

Burada, kolaylık sağlamak için typesafe-enum "kütüphane" dir. Bu başlığı yapıştırın:

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#define ITEM(NAME, VAL) \
struct NAME##_t { \
  std::string toStr() const { return std::string( #NAME ); } \
  int toInt() const { return VAL; } \
}; \
static const NAME##_t NAME = NAME##_t(); \
template <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toInt_visitor(), a);
}

#define ENUM(...) \
typedef boost::variant<__VA_ARGS__>
#endif

Ve şöyle kullanın:

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

Büyünün bir kısmını yok eden A makrosunda ENUM yerine A_t demeniz gerektiğine dikkat edin. Oh iyi. Ayrıca, OP'lerin dizgelere ve satırlara basit dönüşüm gereksinimini karşılamak için artık bir toStr işlevi ve toInt işlevi olduğunu fark edin. Çözemediğim gereksinim, öğeleri yinelemenin bir yoludur. Böyle bir şeyi nasıl yazacağınızı biliyorsanız bana bildirin.

0
Michael Fox

Şu anda https://bitbucket.org/chopsii/typesafe-enums adresinde kendi typesafe enum kütüphanemi yazıyorum

Şimdiye kadarki en deneyimli C++ geliştiricisi değilim, ancak bunu BOOST kasası enums eksiklikleri nedeniyle yazıyorum. 

Bunu kontrol etmekten ve kendiniz kullanmaktan çekinmeyin, ancak bazı (umarım küçük) kullanılabilirlik sorunları vardır ve muhtemelen tüm çapraz platformlarda değildirler.

İsterseniz lütfen katkıda bulunun. Bu benim ilk açık kaynak girişim.

0
Lynden Shields