Une classe d'énumération C ++ peut-elle avoir des méthodes?

145

J'ai une classe enum avec deux valeurs et je veux créer une méthode qui reçoit une valeur et renvoie l'autre. Je veux également maintenir la sécurité des types (c'est pourquoi j'utilise enum class au lieu d'enums).

http://www.cplusplus.com/doc/tutorial/other_data_types/ ne mentionne rien sur les méthodes Cependant, j'avais l'impression que tout type de classe peut avoir des méthodes.

octave
la source
4
Non, ça ne peut pas. Regardez ici .
juanchopanza
@octavian Notez ma réponse et repensez à vos cas d'utilisation s'il vous plaît!
πάντα ῥεῖ
@ πάνταῥεῖ vous avez tout à fait raison, j'ai lu enum mais j'ai pensé à l'union, j'ai tué le commentaire.
Eugen Constantin Dinca
@octavian Demandez - vous même un cas d'utilisation particulier ou vouliez-vous simplement que les restrictions des standards sur C ++ 11 soient enum class/structconfirmées?
πάντα ῥεῖ
J'avais une utilisation en tête ... et c'était la question fondamentale
octave

Réponses:

118

Non, ils ne peuvent pas.

Je peux comprendre que la enum classpartie pour les énumérations fortement typés en C ++ 11 peut sembler impliquer que votre enuma des classtraits aussi, mais ce n'est pas le cas. Ma supposition éclairée est que le choix des mots-clés a été inspiré par le modèle que nous avons utilisé avant C ++ 11 pour obtenir des énumérations de portée:

class Foo {
public:
  enum {BAR, BAZ};
};

Cependant, ce n'est que de la syntaxe. Encore une fois, ce enum classn'est pas un class.

Stefano Sanfilippo
la source
88
Sur ## C ++, on m'a dit que "c ++ vise à être aussi déroutant et convivial que possible" . Évidemment, c'est une blague, mais vous avez l'idée :)
Stefano Sanfilippo
4
A unionn'est pas non plus ce que John Doe considérerait comme une classe . Pourtant, ils peuvent avoir des fonctions de membre. Et les classes ne sont vraiment pas obligatoires pour les fonctions membres. En utilisant un désignateur comme valueou this, quelque chose comme enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; }(ici aussi permettant ;), cela peut avoir autant de sens que d'autres formes de fonctions.
Sebastian Mach
85

Bien que la réponse "vous ne pouvez pas" soit techniquement correcte, je pense que vous pourrez peut-être obtenir le comportement que vous recherchez en utilisant l'idée suivante:

J'imagine que vous voulez écrire quelque chose comme:

Fruit f = Fruit::Strawberry;
f.IsYellow();

Et vous espériez que le code ressemble à ceci:

enum class Fruit : uint8_t
{
  Apple, 
  Pear,
  Banana,
  Strawberry,

  bool IsYellow() { return this == Banana; }
};

...

Mais bien sûr, cela ne fonctionne pas, car les énumérations ne peuvent pas avoir de méthodes (et `` ceci '' ne veut rien dire dans le contexte ci-dessus)

Cependant, si vous utilisez l'idée d'une classe normale contenant une énumération non-classe et une variable membre unique qui contient une valeur de ce type, vous pouvez vous rapprocher extrêmement de la sécurité de syntaxe / comportement / type souhaitée. c'est à dire:

class Fruit
{
public:
  enum Value : uint8_t
  {
    Apple,
    Pear,
    Banana,
    Strawberry
  };

  Fruit() = default;
  constexpr Fruit(Value aFruit) : value(aFruit) { }

#if Enable switch(fruit) use case:
  operator Value() const { return value; }  // Allow switch and comparisons.
                                            // note: Putting constexpr here causes
                                            // clang to stop warning on incomplete
                                            // case handling.
  explicit operator bool() = delete;        // Prevent usage: if(fruit)
#else
  constexpr bool operator==(Fruit a) const { return value == a.value; }
  constexpr bool operator!=(Fruit a) const { return value != a.value; }
#endif

  constexpr bool IsYellow() const { return value == Banana; }

private:
  Value value;
};

Vous pouvez maintenant écrire:

Fruit f = Fruit::Strawberry;
f.IsYellow();

Et le compilateur empêchera des choses comme:

Fruit f = 1;  // Compile time error.

Vous pouvez facilement ajouter des méthodes telles que:

Fruit f("Apple");

et

f.ToString();

peut être pris en charge.

jtlim
la source
1
Ne devrait pas être également IsYellow (), operator ==,! = Marqué comme constexpr?
Jarek C
J'obtiens
18

En se concentrant sur la description de la question au lieu du titre, une réponse possible est

struct LowLevelMouseEvent {
    enum Enum {
        mouse_event_uninitialized = -2000000000, // generate crash if try to use it uninitialized.
        mouse_event_unknown = 0,
        mouse_event_unimplemented,
        mouse_event_unnecessary,
        mouse_event_move,
        mouse_event_left_down,
        mouse_event_left_up,
        mouse_event_right_down,
        mouse_event_right_up,
        mouse_event_middle_down,
        mouse_event_middle_up,
        mouse_event_wheel
    };
    static const char* ToStr (const type::LowLevelMouseEvent::Enum& event)
    {
        switch (event) {
            case mouse_event_unknown:         return "unknown";
            case mouse_event_unimplemented:   return "unimplemented";
            case mouse_event_unnecessary:     return "unnecessary";
            case mouse_event_move:            return "move";
            case mouse_event_left_down:       return "left down";
            case mouse_event_left_up:         return "left up";
            case mouse_event_right_down:      return "right down";
            case mouse_event_right_up:        return "right up";
            case mouse_event_middle_down:     return "middle down";
            case mouse_event_middle_up:       return "middle up";
            case mouse_event_wheel:           return "wheel";
            default:
                Assert (false);
                break;
        }
        return "";
    }
};
Márkus Attila
la source
4

Comme mentionné dans l' autre réponse , non. Ce enum classn'est même pas une classe.


Habituellement, le besoin d'avoir des méthodes pour un enumrésulte de la raison pour laquelle ce n'est pas une énumération régulière (juste incrémentée), mais une sorte de définition au niveau du bit des valeurs à masquer ou qui nécessite d'autres opérations arithmétiques de bits:

enum class Flags : unsigned char {
    Flag1 = 0x01 , // Bit #0
    Flag2 = 0x02 , // Bit #1
    Flag3 = 0x04 , // Bit #3
    // aso ...
}

// Sets both lower bits
unsigned char flags = (unsigned char)(Flags::Flag1 | Flags::Flag2);

// Set Flag3
flags |= Flags::Flag3;

// Reset Flag2
flags &= ~Flags::Flag2;

Évidemment, on pense à encapsuler les opérations nécessaires pour réinitialiser un seul / groupe de bits, par exemple par la valeur du masque de bits ou même des opérations pilotées par index de bits serait utile pour la manipulation d'un tel ensemble de «drapeaux».

le structLa class spécification / prend simplement en charge une meilleure portée des valeurs d'énumération pour l'accès. Ni plus ni moins!

Les moyens de sortir de la restriction que vous ne pouvez pas déclarer de méthodes pour enum (classes) sont, soit d'utiliser une std::bitset(classe wrapper), soit un champ de bitsunion .

unions, et de telles unions de champs de bits peuvent avoir des méthodes (voir ici pour les restrictions!).

J'ai un exemple, comment convertir les valeurs de masque de bits (comme indiqué ci-dessus) en leurs indices de bits correspondants, qui peuvent être utilisés std::bitsetici: BitIndexConverter.hpp
J'ai trouvé cela assez utile pour améliorer la lisibilité de certaines décisions de `` drapeau '' basées algorithmes.

πάντα ῥεῖ
la source
36
Il y a plus de cas d'utilisation qui justifient des méthodes sur enum classe, par exemple toString () et fromString (). Chaque langage majeur moderne (même pas si) a ceci (par exemple C #, Java, Swift), mais pas C ++.
Mike Lischke
1
Espérons une syntaxe d'appel unifiée la prochaine fois ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh
4

Il existe une capacité assez compatible (§) pour refactoriser une énumération dans une classe sans avoir à réécrire votre code, ce qui signifie que vous pouvez effectivement faire ce que vous demandez sans trop de modifications.

(§) comme ElementW le souligne dans un commentaire, le code dépendant de type_traits ne fonctionnera pas, donc par exemple on ne peut pas utiliser auto, etc. Il peut y avoir un moyen de gérer ce genre de choses, mais à la fin, on convertit une énumération en classe, et c'est toujours une erreur de subvertir C ++

les spécifications enum structet enum classconcernent la portée, donc ne font pas partie de cela.

Votre énumération d'origine est par exemple "animal de compagnie" (ceci n'est qu'à titre d'exemple!).

enum pet { 
    fish, cat, dog, bird, rabbit, other 
};

(1) Vous modifiez cela en par exemple petEnum (afin de le cacher de votre code existant).

enum petEnum { 
    fish, cat, dog, bird, rabbit, other 
};

(2) Vous ajoutez une nouvelle déclaration de classe en dessous (nommée avec l'énumération d'origine)

class pet {
    private:
        petEnum value;
        pet() {}

    public:
        pet(const petEnum& v) : value{v} {} //not explicit here.
        operator petEnum() const { return value; }
        pet& operator=(petEnum v) { value = v; return *this;}
        bool operator==(const petEnum v) const { return value == v; }
        bool operator!=(const petEnum v) const { return value != v; }
 //     operator std::string() const;

};

(3) Vous pouvez maintenant ajouter les méthodes de classe que vous aimez à votre classe familière. par exemple. un opérateur de chaîne

    pet::operator std::string() const {
        switch (value) {
            case fish: return "fish";
            case cat:  return "cat";
            case dog:  return "dog";
            case bird: return "bird";
            case rabbit: return "rabbit";
            case other: return "Wow. How exotic of you!";
        }
    }

Vous pouvez maintenant utiliser par exemple std :: cout ...

int main() {
    pet myPet = rabbit;
    if(myPet != fish) {
        cout << "No splashing! ";
    }
    std::cout << "I have a " << std::string(myPet) << std::endl;
    return 0;
}
Konchog
la source
1
Ce n'est pas entièrement compatible: si vous utilisez les valeurs d'énumération avec n'importe quel type de déduction de type où il est prévu d'obtenir unpet nom de type / une instance, qu'il s'agisse de modèles auto, ou decltype, cela casse, lorsque vous obtenez un à la petEnumplace.
ElementW
0

Il ne répond peut-être pas à tous vos besoins, mais avec des opérateurs non membres, vous pouvez toujours vous amuser beaucoup. Par exemple:

#include <iostream>

enum class security_level
{
    none, low, medium, high
};

static bool operator!(security_level s) { return s == security_level::none; }

static security_level& operator++(security_level& s)
{
    switch(s)
    {
        case security_level::none: s = security_level::low; break;
        case security_level::low: s = security_level::medium; break;
        case security_level::medium: s = security_level::high; break;
        case security_level::high: break;
    }
    return s;
}

static std::ostream & operator<<(std::ostream &o, security_level s)
{
    switch(s)
    {
        case security_level::none: return o << "none";
        case security_level::low: return o << "low";
        case security_level::medium: return o << "medium";
        case security_level::high: return o << "high";
    }
}

Cela permet du code comme

security_level l = security_level::none;   
if(!!l) { std::cout << "has a security level: " << l << std::endl; } // not reached
++++l;
if(!!l) { std::cout << "has a security level: " << l << std::endl; } // reached: "medium"
Johannes
la source