Pergunta

É do conhecimento comum que built-in enums em C ++ não são typesafe. Fiquei me perguntando o que as classes que implementam typesafe enums são usados ??lá fora ... Eu mesmo uso o seguinte "bicicleta", mas é um pouco detalhado e limitado:

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

Uso:

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;

Adição: Eu acho que eu deveria ter sido mais específicas sobre os requisitos. Eu vou tentar resumi-los:

Prioridade 1:. Definir uma variável enum para um valor inválido deve ser impossível (um erro em tempo de compilação), sem exceções

Prioridade 2:. Convertendo um valor de enumeração de / para um int deve ser possível com uma única chamada de função / método explícito

Prioridade 3: Como declaração e uso possível

compacto, elegante e conveniente

Prioridade 4:. Convertendo valores enum de e para cordas

Prioridade 5:. (É bom ter) Possibilidade de interagir sobre valores enum

Foi útil?

Solução

Atualmente estou a brincar com a proposta Boost.Enum do impulsionar Vault (filename enum_rev4.6.zip). Embora nunca foi oficialmente apresentado para inclusão no Boost, que é utilizável como está. (Documentação é rara, mas é composta por código fonte clara e bons testes.)

Boost.Enum permite declarar um enum como este:

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")
)

E tê-lo automaticamente expandir para isso:

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

satisfaz todos os cinco prioridades que você lista.

Outras dicas

Um método de compromisso legal é esta:

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

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

Não é typesafe no mesmo sentido que a sua versão é, mas o uso é mais agradável do que enums padrão, e você ainda pode tirar proveito de conversão inteiro quando você precisar dele.

Eu uso C ++ 0x typesafe enums . Eu uso um pouco de modelo helper / macros que fornecem a de / para a funcionalidade string.

enum class Result { Ok, Cancel};

Eu não. De forma demasiada sobrecarga para pouco benefício. Além disso, ser capaz de casta enumerações para diferentes tipos de dados para serialização é uma ferramenta muito útil. Eu nunca vi um caso em que um "tipo seguro" enumeração seria vale a sobrecarga ea complexidade onde ++ ofertas C uma boa implementação suficiente.

A minha opinião é que você está inventando um problema e, em seguida, encaixar uma solução para ele. Eu não vejo nenhuma necessidade de fazer um quadro elaborado por uma enumeração de valores. Se você é dedicada para ter seus valores somente ser membros de um determinado conjunto, você poderia cortar-se uma variante de um conjunto tipo de dados único.

Eu pessoalmente estou usando uma versão adaptada do typesafe enum idioma . Ele não fornece todas as cinco "requisitos" que você indicou em sua edição, mas eu discordo fortemente com alguns deles de qualquer maneira. Por exemplo, eu não vejo como Prio # 4 (conversão de valores para cordas) tem alguma coisa a ver com a segurança de tipos. A maior parte da representação da cadeia de tempo dos valores individuais devem ser separados a partir da definição do tipo de qualquer maneira (acho i18n para uma razão simples). Prio # 5 (iteratio, que é opcional) é uma das coisas mais legais que eu gostaria de ver naturalmente acontecendo em enums, então eu me senti triste que ele aparece como "opcional" em seu pedido, mas parece que melhor é abordada através de uma sistema iteração separado tais como funções begin / end ou um enum_iterator, o que os torna funcionam perfeitamente com STL e C ++ 11 foreach.

OTOH este simples expressão bem fornece Prio # 3 Prio # 1 graças ao fato de que a maioria só envolve enums com mais informações de tipo. para não falar que é uma solução muito simples que em sua maior parte não necessita de quaisquer cabeçalhos de dependência externa, por isso é muito fácil de transportar. Ele também tem a vantagem de fazer enumerações escopo a-la-C ++ 11:

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

O único "buraco" que a solução fornece é que ele não aborda o fato de que ela não impede enums de diferentes tipos (ou um enum e um int) de ser diretamente comparados, porque quando você usa valores diretamente você forçar a conversão implícita para int:

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

Mas até agora eu encontrei esses problemas podem ser resolvidos por simplesmente oferecer uma melhor comparação para o compilador - por exemplo, fornecendo explicitamente um operador que compara qualquer dois tipos enum diferentes, em seguida, forçando-o a falhar:

// 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!");
}

Apesar de não parecem quebrar código até agora, e ele faz para lidar explicitamente com o problema específico sem fazer outra coisa, eu não tenho certeza que tal coisa é uma coisa de uma " deve " fazer (eu suspeito que irá interferir com enums já participando de operadores de conversão declarou em outro lugar, eu ficaria feliz em receber comentários sobre este).

Combinando isso com o acima typesafe idioma dá algo que é relativamente perto de C ++ 11 enum class em humanibility (legibilidade e manutenção) sem ter que fazer qualquer coisa muito obscura. E eu tenho que admitir que foi divertido de fazer, eu nunca tinha pensado realmente perguntar o compilador se eu estava lidando com enums ou não ...

Eu acho que o enum Java seria um bom modelo a seguir. Essencialmente, o formulário Java ficaria assim:

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

    private final String name;

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

    public String getName() {
        return name;
    }
}

O que é interessante sobre a abordagem Java é que OK e CANCEL são imutáveis ??casos, solteirão de Result (com os métodos que você vê). Você não pode criar quaisquer instâncias de Result. Como eles são singletons, você pode comparar pelo ponteiro / referência --- muito útil. : -)

ETA: Em Java, em vez de fazer bitmasks à mão, em vez de utilizar um EnumSet para especificar um conjunto de bits (que implementa a interface Set, e obras como conjuntos --- mas implementados usando bitmasks). Muito mais legível do que a manipulação bitmask escrita à mão!

Eu dei uma resposta a esta aqui , em um tópico diferente. É um estilo diferente de abordagem que permite que a maior parte da mesma funcionalidade sem a necessidade de modificação para a definição enum original (e, consequentemente, permitindo o uso nos casos em que você não definir o enum). Ele também permite a verificação de intervalo de tempo de execução.

A desvantagem da minha abordagem é que ela não programaticamente impor o acoplamento entre a enumeração ea classe auxiliar, então eles têm que ser atualizados em paralelo. Ele trabalha para mim, mas YMMV.

Atualmente, estou escrevendo minha própria biblioteca enumeração typesafe em https://bitbucket.org/chopsii/typesafe- enums

Eu não sou o desenvolvedor mais experiente C ++ sempre, mas eu estou escrevendo este devido às deficiências dos enums BOOST vault.

Sinta-se livre para verificá-la e usá-los a si mesmo, mas eles têm alguns (espero menor) questões de usabilidade, e, provavelmente, não são de todo multi-plataforma.

Por favor, colabore, se você quiser. Esta é a minha primeira empresa de código aberto.

Use boost::variant!

Depois de tentar um monte de idéias acima e encontrá-los falta eu bati sobre esta abordagem simples:

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

Você pode provavelmente vir acima com uma macro para gerar o clichê. (Deixe-me saber se você faz.)

Ao contrário de outras abordagens este é, na verdade, tipo seguro e trabalha com idade C ++. Você mesmo pode fazer tipos legais como boost::variant<int, A_t, B_t, boost::none>, por exemplo, para representar um valor que poderia ser A, B, um inteiro ou nada que é quase Haskell98 níveis de segurança de tipo.

Desvantagens de estar ciente de:

  • at-menos com reforço de idade - estou em um sistema com aumento de 1,33 - você está limitado a 20 itens em sua variante; há uma solução alternativa no entanto
  • afeta tempo de compilação
  • mensagens de erro insano - mas isso é C ++ para você

Atualização

Aqui, para sua conveniência é a sua typesafe-enum "biblioteca". Cole este cabeçalho:

#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

E usá-lo como:

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

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

Observe que você tem a dizer A_t vez de A na macro ENUM que destrói um pouco da magia. Ah bem. Além disso, observe que há agora uma função toStr e uma função toInt para atender PO exigência de conversão simples de strings e ints. A exigência não consigo descobrir uma maneira para iterar sobre os itens. Deixe-me saber se você sabe como escrever uma coisa dessas.

Não tenho certeza se este post é muito tarde, mas há um artigo sobre GameDev.net que satisfaz todos, mas o quinto ponto (capacidade para iterar recenseadores): http://www.gamedev.net/reference/snippets/features/cppstringizing/

O método descrito pelo artigo permite suporte de conversão de string para enumerações existentes sem alterar seu código. Se você quer apenas um apoio para novos enumerações, porém, eu iria com Boost.Enum (mencionados acima).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top