C ++ 'da int numaralandırmanın genel yolu


82

Döküm için genel bir yolu var mı inthiç enumde C++?

Bir intaralığına düşerse enumbir enumdeğer döndürmelidir , aksi takdirde bir exception. Genel olarak yazmanın bir yolu var mı ? Birden fazlası enum typedesteklenmelidir.

Arka plan: Harici bir enum türüne sahibim ve kaynak kodu üzerinde denetimim yok . Bu değeri bir veritabanında saklamak ve geri almak istiyorum.


enum e{x = 10000};Bu durumda yapar 9999aralığında düşecek enum?
Armen Tsirunyan

Hayır, 9999düşmez.
Leonid

9
İyi soru. Herhangi bir "neden" e gelince bu ortaya çıkacak, sadece "serileştirme" dememe izin verin - bana yeterli bir neden gibi görünüyor. Ayrıca C ++ 0x uyumlu bir yanıt duymaktan da mutlu olurum enum class.
Kos

9
"Aralık" burada yanlış kelime, belki "alan"?
Constantin

boost :: numeric_cast <>, değer sınırların dışındaysa pozitif veya negatif bir taşma istisnası atar. Ancak numaralandırma türleri için de uygun olup olmadığından emin değilim. Bunu deneyebilirsin.
yasouser

Yanıtlar:


38

Açık olan şey, numaranıza açıklama eklemektir:

// generic code
#include <algorithm>

template <typename T>
struct enum_traits {};

template<typename T, size_t N>
T *endof(T (&ra)[N]) {
    return ra + N;
}

template<typename T, typename ValType>
T check(ValType v) {
    typedef enum_traits<T> traits;
    const T *first = traits::enumerators;
    const T *last = endof(traits::enumerators);
    if (traits::sorted) { // probably premature optimization
        if (std::binary_search(first, last, v)) return T(v);
    } else if (std::find(first, last, v) != last) {
        return T(v);
    }
    throw "exception";
}

// "enhanced" definition of enum
enum e {
    x = 1,
    y = 4,
    z = 10,
};

template<>
struct enum_traits<e> {
    static const e enumerators[];
    static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};

// usage
int main() {
    e good = check<e>(1);
    e bad = check<e>(2);
}

Dizinin güncel tutulması gerekiyor eki bu, yazarı değilseniz bir sıkıntıdır e. Sjoerd'in dediği gibi, muhtemelen herhangi bir düzgün inşa sistemi ile otomatikleştirilebilir.

Her durumda, 7.2 / 6'ya karşı çıkarsınız:

Emin'in en küçük numaralandırıcı olduğu ve emax'ın en büyük olduğu bir numaralandırma için, numaralandırmanın değerleri, bmin ve bmax'ın sırasıyla en küçük ve en büyük değerler olduğu bmin ila bmax aralığındaki temel tipin değerleridir. emin ve emax depolayabilen bit alanı. Numaralandırıcılarından herhangi biri tarafından tanımlanmamış değerlere sahip bir numaralandırma tanımlamak mümkündür.

Dolayısıyla, yazarı edeğilseniz, değerlerinin egerçekte tanımında göründüğüne dair bir garantiniz olabilir veya olmayabilir .


22

Çirkin.

enum MyEnum { one = 1, two = 2 };

MyEnum to_enum(int n)
{
  switch( n )
  {
    case 1 :  return one;
    case 2 : return two;
  }
  throw something();
}

Şimdi gerçek soru için. Niçin buna ihtiyacın var? Kod çirkin, yazması kolay değil (*?) Ve bakımı kolay değil ve kodunuza dahil edilmesi kolay değil. Yanlış olduğunu söyleyen kod. Neden onunla savaşalım?

DÜZENLE:

Alternatif olarak, numaralandırmaların C ++ 'da integral türler olduğu göz önüne alındığında:

enum my_enum_val = static_cast<MyEnum>(my_int_val);

ama bu, yukarıdakinden daha da çirkin, hatalara çok daha yatkındır ve istediğiniz gibi atmayacaktır.


bu yalnızca bir türü destekler, MyEnum.
Simone

2
@Leonid: Bildiğim kadarıyla genel olarak yapılamaz. Bir düzeyde, throwgeçersiz türler için bulduğunuz herhangi bir çözümün (veya özel bir şey yapmanın) yayınladığım gibi bir anahtarı olması gerekir.
John Dibling

2
Bu neden -1'lendi? Doğru cevap bu. Bazılarının umduğu cevap bu olmadığı için yanlış olduğu anlamına gelmez.
John Dibling

12
A static_cast<MyEnum>da işe yarayacak vereinterpret_cast<MyEnum>
Sjoerd'e

1
Bu yaklaşım işe yarar ve işlevi oluşturmak için bir araç kullanırsanız daha da iyi olur.
Nick

3

Tanımladığınız gibi, değerler bir veritabanındaysa, neden bu tabloyu okuyan ve hem numaralandırma hem de to_enum(int)işlevle bir .h ve .cpp dosyası oluşturan bir kod üreteci yazmıyorsunuz ?

Avantajlar:

  • Bir to_string(my_enum)işlev eklemek kolaydır .
  • Az bakım gerektirir
  • Veritabanı ve kod senkronize

Enum türü kaynak kodu üzerinde denetim yoktur . Dönüşümü gerçekleştiren bir tesisin oluşturulabileceğini kabul ediyorum. Ancak tesis, harici enum türünde yapılan herhangi bir değişiklik / genişletmeden haberdar olmayacaktır (derleme zamanında her seferinde çalıştırılmadıkça).
Leonid

@Leonid Sonra bu enum başlığını okuyun ve buna göre to_enum(int)işlevi oluşturun .
Sjoerd

@Leonid Her ciddi proje yönetim sistemi, makejeneratörün yeniden çalıştırılması gerekip gerekmediğini görmek için iki dosyanın tarihini bile karşılaştırabilir.
Sjoerd

Şimdilik jeneratöre daha basit bir çözümle gideceğimi düşünüyorum. Ama fikir için teşekkürler.
Leonid

Bu şemayı işyerimizde kullanıyoruz, bir araç .hpp kodunu bir şablondan oluşturur, şablon minimumdur.
Nick

3

Hayır- C ++ 'da iç gözlem veya yerleşik "alan denetimi" olanağı yoktur.


2

Bunun hakkında ne düşünüyorsun?

#include <iostream>
#include <stdexcept>
#include <set>
#include <string>

using namespace std;

template<typename T>
class Enum
{
public:
    static void insert(int value)
    {
        _set.insert(value);
    }

    static T buildFrom(int value)
    {
        if (_set.find(value) != _set.end()) {
            T retval;
            retval.assign(value);
            return retval;
        }
        throw std::runtime_error("unexpected value");
    }

    operator int() const { return _value; }

private:
    void assign(int value)
    {
        _value = value;
    }

    int _value;
    static std::set<int> _set;
};

template<typename T> std::set<int> Enum<T>::_set;

class Apples: public Enum<Apples> {};

class Oranges: public Enum<Oranges> {};

class Proxy
{
public:
    Proxy(int value): _value(value) {}

    template<typename T>
    operator T()
    {
        T theEnum;
        return theEnum.buildFrom(_value);
    }

    int _value;
};

Proxy convert(int value)
{
    return Proxy(value);
}

int main()
{    
    Apples::insert(4);
    Apples::insert(8);

    Apples a = convert(4); // works
    std::cout << a << std::endl; // prints 4

    try {
        Apples b = convert(9); // throws    
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
    try {
        Oranges b = convert(4); // also throws  
    }
    catch (std::exception const& e) {
        std::cout << e.what() << std::endl; // prints "unexpected value"
    }
}

Daha sonra değerleri değiştirmek için buraya gönderdiğim kodu kullanabilirsiniz .


Yine de bir Apples::insert(4)yere eklemeniz gerekiyor, bu yüzden bunun bir anahtara göre avantajı yok.
Sjoerd

1
Değerlerin bir veritabanından geldiğini söyledi, bu yüzden bir "ekleme" yöntemi ekledim.
Simone

1

Tarif ettiğiniz gibi bir şeyin var olmasını istememelisiniz, korkarım kod tasarımınızda problemler vardır.

Ayrıca, numaralandırmaların bir aralıkta geldiğini varsayarsınız, ancak bu her zaman böyle değildir:

enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };

Bu bir aralıkta değil: Mümkün olsa bile, 0'dan 2 ^ n'ye kadar olan her tamsayıyı bir numaralandırma değeriyle eşleşip eşleşmediklerini kontrol etmeniz mi gerekiyor?


aksi takdirde enum değerlerini veritabanından nasıl alırdınız? Tamsayılar derleme zamanında biliniyor, öyleyse neden şablonlara dayalı genel bir dönüşüme sahip olmak mümkün değil?
Leonid

2
@Leonid: Çünkü bir düzeyde, dediğim gibi bir anahtarın olması gerekiyor.
John Dibling

2
@Leonid Şablonları aklınıza gelebilecek her sorunu çözmek için sihirli değnek değildir.
Sjoerd

John haklı. İstediğinizi yapmak için bir sıralamadan daha karmaşık bir türe ihtiyacınız var, bence bu bir sınıf hiyerarşisi ile mümkün.
Simone

Sınıf hiyerarşisini kullanan bir çözüm yayınladım, kontrol edin.
Simone

1

Enum değerlerinizi şablon parametreleri olarak listelemeye hazırsanız, bunu C ++ 11'de varadic şablonlarla yapabilirsiniz. Buna, farklı bağlamlarda geçerli enum değerlerinin alt kümelerini kabul etmenize izin veren iyi bir şey olarak bakabilirsiniz; dış kaynaklardaki kodları ayrıştırırken genellikle yararlıdır.

Belki de istediğiniz kadar genel olmayabilir, ancak kontrol kodunun kendisi genelleştirilmiştir, sadece değerler kümesini belirlemeniz gerekir. Bu yaklaşım boşlukları, keyfi değerleri vb. Ele alır.

template<typename EnumType, EnumType... Values> class EnumCheck;

template<typename EnumType> class EnumCheck<EnumType>
{
public:
    template<typename IntType>
    static bool constexpr is_value(IntType) { return false; }
};

template<typename EnumType, EnumType V, EnumType... Next>
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
{
    using super = EnumCheck<EnumType, Next...>;

public:
    template<typename IntType>
    static bool constexpr is_value(IntType v)
    {
        return v == static_cast<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
    }

    EnumType convert(IntType v)
    {
        if (!is_value(v)) throw std::runtime_error("Enum value out of range");
        return static_cast<EnumType>(v);
};

enum class Test {
    A = 1,
    C = 3,
    E = 5
};

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;

void check_value(int v)
{
    if (TestCheck::is_value(v))
        printf("%d is OK\n", v);
    else
        printf("%d is not OK\n", v);
}

int main()
{
    for (int i = 0; i < 10; ++i)
        check_value(i);
}

Bu bağlantı soruyu cevaplayabilirken, cevabın temel kısımlarını buraya eklemek ve referans için bağlantıyı sağlamak daha iyidir. Bağlantılı sayfa değişirse yalnızca bağlantı yanıtları geçersiz hale gelebilir. - Yorumdan
Tas

1
@Tas Bu, farklı bir SO cevabına bağlantıdır - harici bir bağlantının sahip olduğu sorunların aynısına sahip değildir. Yine de güncellendi.
14:19

0

"Çirkin" sürüme alternatif C ++ 0x, birden çok numaralandırmaya izin verir. Anahtarlar yerine başlatıcı listeleri kullanır, biraz daha temiz IMO. Maalesef bu, enum değerlerini sabit kodlama ihtiyacını ortadan kaldırmaz.

#include <cassert>  // assert

namespace  // unnamed namespace
{
    enum class e1 { value_1 = 1, value_2 = 2 };
    enum class e2 { value_3 = 3, value_4 = 4 };

    template <typename T>
    int valid_enum( const int val, const T& vec )
    {
        for ( const auto item : vec )
            if ( static_cast<int>( item ) == val ) return val;

        throw std::exception( "invalid enum value!" );  // throw something useful here
    }   // valid_enum
}   // ns

int main()
{
    // generate list of valid values
    const auto e1_valid_values = { e1::value_1, e1::value_2 };
    const auto e2_valid_values = { e2::value_3, e2::value_4 };

    auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
    assert( result1 == e1::value_1 );

    auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
    assert( result2 == e2::value_3 );

    // test throw on invalid value
    try
    {
        auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
        assert( false );
    }
    catch ( ... )
    {
        assert( true );
    }
}
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.