Question

Il est de notoriété publique que les énumérations intégrées à C ++ ne sont pas dactylographiées. Je me demandais quelles classes implémentant des enums typesafe sont utilisées ... J'utilise moi-même le & "Vélo &"; Mais il est quelque peu verbeux et limité:

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

Utilisation:

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;

Ajout: Je pense que j'aurais dû être plus spécifique sur les exigences. Je vais essayer de les résumer:

Priorité 1: Définir une variable enum sur une valeur non valide doit être impossible (une erreur de compilation) sans exception.

Priorité 2: La conversion d'une valeur enum en / à partir d'un int devrait être possible avec un seul appel de fonction / méthode explicite.

Priorité 3: Déclaration et utilisation aussi compactes, élégantes et pratiques que possible

Priorité 4: Conversion des valeurs enum en et à partir de chaînes.

Priorité 5: (agréable à avoir) Possibilité de parcourir les valeurs enum.

Était-ce utile?

La solution

Je joue actuellement avec la proposition Boost.Enum de la Boost Vault (nom de fichier enum_rev4.6.zip). Bien qu'il n'ait jamais été officiellement soumis pour inclusion dans Boost, il est utilisable tel quel. (La documentation manque, mais elle est compensée par un code source clair et de bons tests.)

Boost.Enum vous permet de déclarer un enum comme ceci:

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

Et faites-le s'étendre automatiquement à ceci:

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

Il répond aux cinq priorités que vous avez énumérées.

Autres conseils

Une bonne méthode de compromis est la suivante:

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

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

Il n’est pas typé dans le même sens que votre version, mais son utilisation est meilleure que celle d’énums standard et vous pouvez toujours tirer parti de la conversion d’entiers quand vous en avez besoin.

J'utilise les types énumérés C ++ 0x . J'utilise des modèles / macros d'aide qui fournissent la fonctionnalité de chaîne de / à.

enum class Result { Ok, Cancel};

Je ne le fais pas. Beaucoup trop de frais généraux pour peu d'avantages. De plus, être capable de classer des énumérations dans différents types de données pour la sérialisation est un outil très pratique. Je n'ai jamais vu de cas où un & "Type safe &"; l'énumération vaudrait les frais généraux et la complexité où C ++ offre déjà une assez bonne implémentation.

Mon avis est que vous inventez un problème et que vous y apportez une solution. Je ne vois pas la nécessité de créer un cadre élaboré pour une énumération de valeurs. Si vous êtes dédié à ce que vos valeurs soient uniquement membres d'un certain ensemble, vous pouvez pirater une variante d'un type de données d'ensemble unique.

J'utilise personnellement une version adaptée de la typesafe enum idiom . Il ne fournit pas les cinq & Quot; exigences & Quot; que vous avez dit dans votre édition, mais je suis fortement en désaccord avec certains d'entre eux de toute façon. Par exemple, je ne vois pas comment Prio # 4 (conversion de valeurs en chaînes) a quelque chose à voir avec le type safety. La plupart des représentations temporelles de valeurs individuelles doivent toujours être séparées de la définition du type (pensez à i18n pour une raison simple). Le Prio n ° 5 (option facultative) est l'une des choses les plus agréables que j'aimerais voir se produire naturellement dans les énumérations. Je me suis donc senti triste qu'elle apparaisse comme & «Facultatif»! > quot; dans votre demande, mais il semble que ce soit mieux traité via un système d'itération séparé telles que les fonctions begin / end ou un enum_iterator, ce qui les rend compatibles avec STL et C ++ 11 foreach.

OTOH cet idiome simple fournit joliment le Prio # 3 Prio # 1 en raison du fait qu’il enveloppe uniquement les enum s avec plus d’informations sur le type. Sans compter que c'est une solution très simple qui, pour la plupart, ne nécessite aucun en-tête de dépendance externe, il est donc assez facile à transporter. Il présente également l’avantage de faire des énumérations étendues à 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;

Le seul & "trou &"; cette solution fournit est qu'elle ne résout pas le fait qu'elle n'empêche pas la comparaison directe de int différents types (ou un enum class et un entier), car lorsque vous utilisez des valeurs directement, vous forcez l'implicite conversion en <=>:

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

Mais jusqu'à présent, j'ai trouvé que de tels problèmes peuvent être résolus en offrant simplement une meilleure comparaison avec le compilateur - par exemple, en fournissant explicitement un opérateur comparant deux types <=> différents, puis en forçant son échec:

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

Bien que le code ne semble pas casser le code jusqu'à présent et qu'il résolve explicitement le problème spécifique sans faire autre chose, je ne suis pas sûr qu'il s'agisse d'une chose telle que & "; devrait " fais (je soupçonne que cela va interférer avec la participation de <=> déjà à des opérateurs de conversion déclarés ailleurs; je serais ravi de recevoir des commentaires à ce sujet).

En combinant cela avec l'idiome de typesafe ci-dessus, vous obtenez quelque chose qui est relativement proche de C ++ 11 <=> en humanité (lisibilité et maintenabilité) sans rien faire de trop obscur. Et je dois admettre que c'était amusant à faire, je n’avais jamais pensé à réellement demander au compilateur si j’avais affaire à <=> s ou non ...

Je pense que Java enum serait un bon modèle à suivre. Pour l’essentiel, le formulaire Java ressemblerait à ceci:

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

    private final String name;

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

    public String getName() {
        return name;
    }
}

Ce qui est intéressant dans l’approche Java, c’est que OK et CANCEL sont des instances uniques et immuables de Result (avec les méthodes que vous voyez). Vous ne pouvez créer aucune autre instance de EnumSet. Comme ce sont des singletons, vous pouvez comparer par pointeur / référence - très pratique. : -)

ETA: En Java, au lieu de faire des masques de bits à la main, vous utilisez plutôt un Set pour spécifier un jeu de bits (il implémente l'interface <=> et fonctionne comme un jeu --- mais implémenté avec des masques de bits). Beaucoup plus lisible que la manipulation de masque binaire écrite à la main!

J'ai répondu à cette question ici , sur un sujet différent. Il s'agit d'un style d'approche différent qui permet la plupart des mêmes fonctionnalités sans nécessiter de modification de la définition de l'énumération d'origine (et donc, permettant son utilisation dans les cas où vous ne définissez pas l'énum). Il permet également de vérifier les plages d’exécution.

L’inconvénient de mon approche est qu’elle n’impose pas de manière programmée le couplage entre la classe enum et la classe d’assistance, elles doivent donc être mises à jour en parallèle. Cela fonctionne pour moi, mais YMMV.

Je suis en train d'écrire ma propre bibliothèque enum typesafe à l'adresse https://bitbucket.org/chopsii/typesafe- enums

Je ne suis pas le développeur C ++ le plus expérimenté de tous les temps, mais je l’écris en raison des faiblesses de l’énumération du coffre-fort BOOST.

N'hésitez pas à les consulter et à les utiliser vous-même, mais ils ont quelques problèmes d'utilisabilité (espérons-le mineurs) et ne sont probablement pas du tout multi-plateformes.

Merci de contribuer si vous le souhaitez. Ceci est ma première entreprise open source.

Utilisez boost::variant!

Après avoir essayé beaucoup des idées ci-dessus et les avoir trouvées manquantes, je suis tombé sur cette approche simple:

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

Vous pouvez probablement créer une macro pour générer le passe-partout. (Faites-moi savoir si vous le faites.)

Contrairement à d’autres approches, celle-ci est en fait compatible avec le texte et fonctionne avec l’ancien C ++. Vous pouvez même créer des types sympas tels que boost::variant<int, A_t, B_t, boost::none>, par exemple, pour représenter une valeur pouvant être A, B, un entier ou rien qui correspond presque au niveau de sécurité de Haskell98.

Inconvénients à prendre en compte:

  • au moins avec l'ancien boost - je suis sur un système avec boost 1.33 - vous êtes limité à 20 objets dans votre variante; il y a une solution de contournement cependant
  • affecte le temps de compilation
  • messages d'erreur insensés - mais c'est du C ++ pour vous

Mettre à jour

Ici, pour votre commodité, votre fichier typesafe-enum & "; bibliothèque &"; Collez cet en-tête:

#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

Et utilisez-le comme:

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

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

Notez que vous devez indiquer A_t au lieu de A dans la macro ENUM qui détruit une partie de la magie. Tant pis. Notez également qu’il existe désormais une fonction toStr et une toInt fonction permettant de répondre aux exigences des opérateurs en matière de conversion simple en chaînes et en éléments. L'exigence que je n'arrive pas à comprendre est un moyen de parcourir les éléments. Faites-moi savoir si vous savez comment écrire une telle chose.

Je ne sais pas si ce message est trop tardif, mais il existe un article sur GameDev.net qui satisfait tout sauf le cinquième point (possibilité de parcourir les énumérateurs): http://www.gamedev.net/reference/snippets/features/cppstringizing/

La méthode décrite dans l'article autorise la prise en charge de la conversion de chaîne pour les énumérations existantes sans modifier leur code. Si vous souhaitez uniquement prendre en charge de nouvelles énumérations, utilisez Boost.Enum (mentionné ci-dessus).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top