¿Puede una clase de enumeración de C++ tener métodos?

10 minutos de lectura

avatar de usuario
bsky

Tengo una clase de enumeración con dos valores y quiero crear un método que reciba un valor y devuelva el otro. También quiero mantener la seguridad de los tipos (es por eso que uso la clase de enumeración en lugar de las enumeraciones).

http://www.cplusplus.com/doc/tutorial/other_data_types/ no menciona nada sobre métodos Sin embargo, tenía la impresión de que cualquier tipo de clase puede tener métodos.

  • No, no puede. Ver aquí.

    – juanchopanza

    22 de enero de 2014 a las 23:05

  • @octavio Nota ¡mi respuesta y reconsidere sus casos de uso, por favor!

    – πάντα ῥεῖ

    22 de enero de 2014 a las 23:41

  • @πάνταῥεῖ tienes toda la razón, he leído la enumeración pero pensé que la unión eliminó el comentario.

    – Eugen Constantino Dinca

    23 de enero de 2014 a las 1:10

  • @octavio Son incluso solicitó un caso de uso particular, o simplemente quería tener las restricciones estándar en c ++ 11 enum class/struct ¿confirmado?

    – πάντα ῥεῖ

    23 de enero de 2014 a las 1:19


  • Tenía un uso en mente… y este era el tema fundamental

    – bsky

    23 de enero de 2014 a las 1:51

avatar de usuario
Stefano Sanfilippo

No, no pueden.

Puedo entender que el enum class parte para enumeraciones fuertemente tipadas en C++11 podría parecer que implica que su enum posee class rasgos también, pero no es el caso. Mi conjetura es que la elección de las palabras clave se inspiró en el patrón que usamos antes de C++ 11 para obtener enumeraciones de ámbito:

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

Sin embargo, eso es solo sintaxis. Otra vez, enum class no es un class.

  • En ##C++ me dijeron que “c++ pretende ser lo más confuso y experto posible”. Obviamente es una broma, pero entiendes la idea 🙂

    – Stefano Sanfilippo

    22 de enero de 2014 a las 23:20


  • A union no es lo que John Doe consideraría un clase, también. Sin embargo, pueden tener funciones miembro. Y las clases realmente no son obligatorias para las funciones miembro. Usando un designador como value o thisalgo como enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; } (aquí también permitiendo ;), puede tener tanto sentido como otras formas de funciones.

    – Sebastián Mach

    22 de septiembre de 2017 a las 12:07

  • ¿Por qué no fue esto? struct Foo{ enum {BAR, BAZ}; }; y por lo tanto enum struct?

    – Moberg

    19 de enero de 2021 a las 8:43

  • Vaya, enum struct es en realidad una forma válida de crear una enumeración con ámbito. ¡Quien sabe!

    – Moberg

    19 de enero de 2021 a las 9:11


avatar de usuario
jtlim

Si bien la respuesta de que “no puede” es técnicamente correcta, creo que puede lograr el comportamiento que está buscando utilizando la siguiente idea:

Me imagino que quieres escribir algo como:

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

Y esperabas que el código se viera así:

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

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

Pero, por supuesto, no funciona, porque las enumeraciones no pueden tener métodos (y ‘esto’ no significa nada en el contexto anterior)

Sin embargo, si usa la idea de una clase normal que contiene una enumeración que no es de clase y una sola variable de miembro que contiene un valor de ese tipo, puede acercarse mucho a la seguridad de sintaxis/comportamiento/tipo que desea. es decir:

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:
  // Allow switch and comparisons.
  constexpr operator Value() const { return value; }

  // Prevent usage: if(fruit)
  explicit operator bool() const = delete;        
#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;
};

Ahora puedes escribir:

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

Y el compilador evitará cosas como:

Fruit f = 1;  // Compile time error.

Podría agregar fácilmente métodos tales que:

Fruit f("Apple");

y

f.ToString();

puede ser compatible.

  • ¿No debería estar también IsYellow(), operator==, != marcado como constexpr?

    – Jarek C.

    14 de mayo de 2019 a las 6:57

  • Recibo ” error: falta el operador binario antes del token “cambiar” “

    – Pedro77

    27 de febrero de 2020 a las 16:46

  • Esto es muy inteligente. ¿Hay alguna manera de que la enumeración dentro de la clase también sea un enum class?

    – Joan Marcè i Igual

    17 mayo 2021 a las 20:49

  • ¿Podría dar más detalles sobre la parte macro if-else?

    – Iizuki

    27 de mayo de 2021 a las 11:12

  • Suyo señor, es la mejor respuesta hasta ahora.

    – Shirkam

    25 de agosto de 2021 a las 12:20

avatar de usuario
Markus Attila

Concentrándose en la descripción de la pregunta en lugar del título, una posible respuesta es

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

avatar de usuario
Konchog

Hay un habilidad bastante compatible(§) para refactorizar una enumeración en una clase sin tener que volver a escribir su código, lo que significa que efectivamente puedes haga lo que estaba pidiendo hacer sin demasiada edición.

(§) como señala ElementW en un comentario, rasgos_tipo el código dependiente no funcionará, por lo que, por ejemplo, no se puede usar auto, etc. Puede haber alguna forma de manejar este tipo de cosas, pero al final uno está convirtiendo una enumeración en una clase, y siempre es un error subvertir C++

la enum struct y enum class las especificaciones se refieren al alcance, por lo que no forman parte de esto.

Su enumeración original es, por ejemplo, ‘mascota’ (¡esto es solo como ejemplo!).

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

(1) Lo modifica a, por ejemplo, petEnum (para ocultarlo de su código existente).

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

(2) Agrega una nueva declaración de clase debajo (nombrada con la enumeración original)

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) Ahora puede agregar los métodos de clase que desee a su clase mascota. p.ej. un operador de cadena

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

Ahora puede usar, por ejemplo, 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;
}

avatar de usuario
πάντα ῥεῖ

Como se menciona en la otra respuesta, no. Incluso enum class no es una clase


Por lo general, el necesitar tener métodos para una enum resulta de la razón de que no es un regular (simplemente incrementando) enumeración, pero una especie de definición bit a bit de valores para enmascarar o necesitar otras operaciones aritméticas 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;

Obviamente, uno piensa en encapsular las operaciones necesarias para restablecer/establecer un solo/grupo de bits, por ejemplo, mediante el valor de la máscara de bits o incluso las operaciones impulsadas por el índice de bits serían útiles para la manipulación de dicho conjunto de ‘banderas’.

El c ++ 11 struct/class especificación solo admite un mejor alcance de los valores de enumeración para el acceso. ¡Ni mas ni menos!

Maneras de salir de la restricción que no se pueden declarar métodos para enumeración (clases) son, ya sea para usar un std::bitset (clase contenedora), o una campo de bits union.

unions, y tales uniones de campo de bits pueden tienen métodos (ver aquí para las restricciones!).

Tengo una muestra, cómo convertir valores de máscara de bits (como se muestra arriba) a sus índices de bits correspondientes, que se pueden usar a lo largo de un std::bitset aquí: BitIndexConverter.hpp

Encontré esto bastante útil para mejorar la legibilidad de algunos algoritmos basados ​​​​en decisiones de ‘bandera’.

  • Hay más casos de uso que justifican métodos en la clase de enumeración, por ejemplo, toString() y fromString(). Todos los lenguajes principales modernos (aunque no tanto) tienen esto (por ejemplo, C#, Java, Swift), pero no C++.

    – Mike Lischke

    20 de julio de 2015 a las 13:35

  • Esperemos una sintaxis de llamada unificada la próxima vez… open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf

    – sdgfsdh

    20 de diciembre de 2016 a las 10:26

avatar de usuario
johannes

Puede que no satisfaga todas sus necesidades, pero con los operadores que no son miembros todavía puede divertirse mucho. Por ejemplo:

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

Esto permite código como

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"

  • Hay más casos de uso que justifican métodos en la clase de enumeración, por ejemplo, toString() y fromString(). Todos los lenguajes principales modernos (aunque no tanto) tienen esto (por ejemplo, C#, Java, Swift), pero no C++.

    – Mike Lischke

    20 de julio de 2015 a las 13:35

  • Esperemos una sintaxis de llamada unificada la próxima vez… open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf

    – sdgfsdh

    20 de diciembre de 2016 a las 10:26

Basado en la respuesta de jtlim

Idea (Solución)

enum ErrorType: int {
  noConnection,
  noMemory
};

class Error {
public:
  Error() = default;
  constexpr Error(ErrorType type) : type(type) { }

  operator ErrorType() const { return type; }
  constexpr bool operator == (Error error) const { return type == error.type; }
  constexpr bool operator != (Error error) const { return type != error.type; }    
  constexpr bool operator == (ErrorType errorType) const { return type == errorType; }
  constexpr bool operator != (ErrorType errorType) const { return type != errorType; }

  String description() { 
    switch (type) {
    case noConnection: return "no connection";
    case noMemory: return "no memory";
    default: return "undefined error";
    }
 }

private:
  ErrorType type;
};

Uso

Error err = Error(noConnection);
err = noMemory;
print("1 " + err.description());

switch (err) {
  case noConnection: 
    print("2 bad connection");
    break;
  case noMemory:
    print("2 disk is full");
    break;
  default: 
    print("2 oops");
    break;
}

if (err == noMemory) { print("3 Errors match"); }
if (err != noConnection) { print("4 Errors don't match"); }

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad