¿Puede una clase enum de C ++ tener métodos?

145

Tengo una clase enum con dos valores, y quiero crear un método que reciba un valor y devuelva el otro. También quiero mantener la seguridad de tipo (por eso uso enum class en lugar de enums).

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.

octaviano
fuente
44
No, no puede. Ver aquí .
juanchopanza
@octavian ¡ Tenga en cuenta mi respuesta y reconsidere sus casos de uso, por favor!
πάντα ῥεῖ
@ πάνταῥεῖ tienes toda la razón, he leído enum pero pensé en union, maté el comentario.
Eugen Constantin Dinca
@octavian ¿ Incluso está pidiendo un caso de uso en particular o simplemente desea que se confirmen las restricciones estándar en c ++ 11 enum class/struct ?
πάντα ῥεῖ
Tenía un uso en mente ... y este era el tema fundamental
octaviano

Respuestas:

118

No, ellos no pueden.

Puedo entender que la enum classparte para enumeraciones fuertemente tipadas en C ++ 11 podría parecer que implica que también enumtiene classrasgos, 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 alcance:

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

Sin embargo, eso es solo sintaxis. De nuevo, enum classno es un class.

Stefano Sanfilippo
fuente
88
En ## C ++ me dijeron que "c ++ pretende ser lo más confuso y amigable posible" . Obviamente es una broma, pero tienes la idea :)
Stefano Sanfilippo
44
A uniontampoco es lo que John Doe consideraría una clase . Sin embargo, pueden tener funciones miembro. Y las clases realmente no son obligatorias para las funciones de los miembros. Usando un designador como valueo this, algo así enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; }(aquí también lo permite ;), puede tener tanto sentido como otras formas de funciones.
Sebastian Mach
85

Si bien la respuesta de que "no puedes" es técnicamente correcta, creo que puedes lograr el comportamiento que estás buscando usando 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 variable de miembro único que contiene un valor de ese tipo, puede acercarse mucho a la seguridad de sintaxis / comportamiento / tipo que desee. 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:
  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;
};

Ahora puedes escribir:

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

Y el compilador evitará cosas como:

Fruit f = 1;  // Compile time error.

Puede agregar fácilmente métodos tales como:

Fruit f("Apple");

y

f.ToString();

puede ser apoyado

jtlim
fuente
1
¿No debería ser también IsYellow (), operator ==,! = Marcado como constexpr?
Jarek C
Recibo un "error: falta el operador binario antes del token" switch ""
Pedro77
18

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 "";
    }
};
Márkus Attila
fuente
4

Como se menciona en la otra respuesta , no. Ni siquiera enum classes una clase.


Por lo general, la necesidad de tener métodos para obtener enumresultados por la razón de que no es un enumeración regular (solo incremental), sino de una definición de valores a nivel de bits para enmascarar o necesita otras operaciones de aritmética 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 re / establecer un solo / grupo de bits, por ejemplo, el valor de la máscara de bits o incluso las operaciones controladas por el índice de bits serían útiles para la manipulación de un conjunto de 'banderas' de este tipo.

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

Las formas de salir de la restricción que no puede declarar métodos para la enumeración (clases) son usar una std::bitset(clase de envoltura) o un campo de bitsunion .

unions, y tales uniones de campo de bits pueden tener métodos (¡vea aquí las restricciones!).

Tengo una muestra, cómo convertir los valores de máscara de bits (como se muestra arriba) a sus índices de bits correspondientes, que se pueden usar a lo largo de std::bitsetaquí: BitIndexConverter.hpp
He encontrado esto bastante útil para mejorar la legibilidad de algunos 'bandera' basado en la decisión algoritmos

πάντα ῥεῖ
fuente
36
Hay más casos de uso que garantizan métodos en enum classe, por ejemplo, toString () y fromString (). Cada lenguaje principal moderno (incluso no tan) tiene esto (por ejemplo, C #, Java, Swift), simplemente no C ++.
Mike Lischke
1
Esperemos una sintaxis de llamada unificada la próxima vez ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh
4

Hay una capacidad bastante compatible (§) para refactorizar una enumeración en una clase sin tener que reescribir su código, lo que significa que efectivamente puede hacer lo que estaba pidiendo sin demasiada edición.

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

las especificaciones enum structy enum classson sobre el alcance, por lo que no forman parte de esto.

Su enumeración original es, por ejemplo, 'mascota' (¡esto es solo un ejemplo!).

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

(1) Modifica eso 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 de ella (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 de 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;
}
Konchog
fuente
1
Es no totalmente compatible: si utiliza los valores de enumeración con cualquier tipo de tipo de deducción cuando se espera para obtener un petnombre de tipo / instancia, ya sea de plantillas, autoo decltype, esto rompe, ya que tienes un petEnumlugar.
ElementW
0

Puede que no satisfaga todas sus necesidades, pero con operadores que no son miembros aún 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"
Johannes
fuente