web-dev-qa-db-fra.com

Une manière appropriée d'associer des enums avec des chaînes

Supposons que j'utilise souvent un certain nombre de chaînes tout au long de mon programme (pour stocker l'état et des choses de ce genre). Les opérations sur les chaînes peuvent être coûteuses, aussi, chaque fois que vous y adressez, je voudrais utiliser une énumération. J'ai vu quelques solutions jusqu'à présent:

typedef enum {
    STRING_HELLO = 0,
    STRING_WORLD
} string_enum_type;

// Must be in sync with string_enum_type
const char *string_enumerations[] = {
    "Hello",
    "World"
}

L'autre que je rencontre assez souvent:

typedef enum {
    STRING_HELLO,
    STRING_WORLD
} string_enum_type;

const char *string_enumerations[] = {
    [STRING_HELLO] = "Hello",
    [STRING_WORLD] = "World"
}

Quels sont les avantages/inconvénients de ces deux méthodes? Y en a t-il un meilleur?

19
PoVa

Le seul avantage de l'ancien est qu'il est compatible avec les anciennes normes C.

En dehors de cela, la dernière alternative est supérieure, car elle assure l'intégrité des données même si l'énum est modifié ou les éléments changent de place. Cependant, il convient de le compléter par une vérification pour s'assurer que le nombre d'éléments dans l'énumération correspond au nombre d'éléments dans la table de recherche:

typedef enum {
    STRING_HELLO,
    STRING_WORLD,
    STRING_N  // counter
} string_enum_type;

const char *string_enumerations[] = {
    [STRING_HELLO] = "Hello",
    [STRING_WORLD] = "World"
};

_Static_assert(sizeof string_enumerations/sizeof *string_enumerations == STRING_N,
               "string_enum_type does not match string_enumerations");

Ce qui précède est la meilleure méthode pour un simple couplage "énumération - table de conversion". Une autre option consisterait à utiliser des structures, mais cela conviendrait mieux à des types de données plus complexes.


Et enfin, plus encore, la 3ème version consisterait à utiliser des "macros X". Ceci n'est pas recommandé sauf si vous avez des exigences particulières concernant la répétition du code et la maintenance. Je l'inclurai ici pour compléter, mais je ne le recommande pas dans le cas général:

#define STRING_LIST          \
 /* index         str    */  \
  X(STRING_HELLO, "Hello")   \
  X(STRING_WORLD, "World")


typedef enum {
  #define X(index, str) index,
    STRING_LIST
  #undef X
  STRING_N // counter
} string_enum_type;


const char *string_enumerations[] = {
  #define X(index, str) [index] = str,
    STRING_LIST
  #undef X
};

_Static_assert(sizeof string_enumerations/sizeof *string_enumerations == STRING_N,
               "string_enum_type does not match string_enumerations");
14
Lundin

Une autre possibilité pourrait être d'utiliser une fonction plutôt qu'un tableau:

const char *enumtostring(string_enum_type e) {
    switch(e) {
        case STRING_HELLO: return "hello";
        case STRING_WORLD: return "world";
    }
}

gcc, au moins, vous avertira si vous ajoutez une valeur enum mais oubliez d'ajouter le cas du commutateur correspondant.

(Je suppose que vous pourriez aussi essayer de créer ce type de fonction inline.)


Addendum: l'avertissement gcc que j'ai mentionné ne s'applique que si l'instruction switch a pas un cas default. Donc, si vous voulez imprimer quelque chose pour les valeurs hors limites qui se glissent, vous pouvez le faire, pas avec un cas default, mais avec quelque chose comme ceci:

const char *enumtostring(string_enum_type e) {
    switch(e) {
        case STRING_HELLO: return "hello";
        case STRING_WORLD: return "world";
    }
    return "(unrecognized string_enum_type value)";
}

Il est également agréable d'inclure la valeur hors limites:

    static char tmpbuf[50];
    snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
    return tmpbuf;

(Ce dernier fragment a quelques limitations supplémentaires, mais cet addendum est déjà long, je ne vais donc pas en parler tout de suite.)

3
Steve Summit

Une autre possibilité consiste à utiliser #defines

Malgré les nombreux inconvénients de son utilisation, l'avantage principal est que #defines ne prend aucune place s'il n'est pas utilisé ... 

#define STRING_HELLO "Hello"
#define STRING_WORLD "World"
0
Alejandro Blasco