Una classe enum C ++ può avere metodi?


145

Ho una classe enum con due valori e voglio creare un metodo che riceve un valore e restituisce l'altro. Voglio anche mantenere la sicurezza dei tipi (ecco perché uso enum class invece di enums).

http://www.cplusplus.com/doc/tutorial/other_data_types/ non menziona nulla sui metodi Tuttavia, ho avuto l'impressione che qualsiasi tipo di classe possa avere metodi.


4
No non può. Vedi qui .
juanchopanza,

@octavian Nota la mia risposta e ripensaci sui tuoi casi d'uso per favore!
πάντα ῥεῖ il

@ πάνταῥεῖ hai perfettamente ragione, ho letto enum ma ho pensato al sindacato, ho ucciso il commento.
Eugen Constantin Dinca,

@octavian Stai addirittura chiedendo un caso d'uso particolare o volevi semplicemente confermare le restrizioni standard su c ++ 11 enum class/struct ?
πάντα ῥεῖ

Avevo in mente ... e questo era il problema fondamentale
Ottaviano

Risposte:


118

No, non possono.

Posso capire che la enum classparte per enumerazioni fortemente tipizzati in C ++ 11 potrebbe sembrare implicare che il vostro enumha classtratti troppo, ma non è il caso. La mia ipotesi educata è che la scelta delle parole chiave sia stata ispirata dal modello che abbiamo usato prima di C ++ 11 per ottenere enumerazioni di ambito:

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

Tuttavia, questa è solo sintassi. Ancora una volta, enum classnon è un class.


88
Su ## C ++ mi è stato detto che "c ++ mira ad essere il più confuso ed amichevole possibile per gli esperti" . Ovviamente è uno scherzo, ma ti viene l'idea :)
Stefano Sanfilippo,

4
A unionnon è quello che John Doe considererebbe anche una classe . Tuttavia possono avere funzioni membro. E le classi non sono realmente obbligatorie per le funzioni membro. Usando un designatore come valueo thisqualcosa del genere enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; }(anche qui permettendo ;), può avere lo stesso senso di altre forme di funzioni.
Sebastian Mach,

85

Mentre la risposta che "non puoi" è tecnicamente corretta, credo che potresti essere in grado di raggiungere il comportamento che stai cercando usando la seguente idea:

Immagino che tu voglia scrivere qualcosa del tipo:

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

E speravi che il codice fosse simile al seguente:

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

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

...

Ma ovviamente, non funziona, perché gli enum non possono avere metodi (e 'this' non significa nulla nel contesto sopra)

Tuttavia, se si utilizza l'idea di una classe normale contenente un enum non di classe e una variabile a membro singolo che contiene un valore di quel tipo, è possibile avvicinarsi molto alla sintassi / comportamento / sicurezza del tipo che si desidera. vale a 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;
};

Ora puoi scrivere:

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

E il compilatore impedirà cose come:

Fruit f = 1;  // Compile time error.

È possibile aggiungere facilmente metodi in modo che:

Fruit f("Apple");

e

f.ToString();

può essere supportato.


1
Non dovrebbe essere anche IsYellow (), operator ==,! = Contrassegnato come constexpr?
Jarek C

Ricevo "errore: operatore binario mancante prima del token" switch ""
Pedro77

18

Concentrarsi sulla descrizione della domanda anziché sul titolo è una possibile risposta

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

4

Come menzionato nell'altra risposta , no. Anche enum classnon è una classe.


Di solito la necessità di avere metodi per un enumrisultato deriva dal fatto che non è un enum regolare (solo incrementale), ma un tipo di definizione bit a bit di valori da mascherare o che necessita di altre operazioni bit-aritmetiche:

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;

Ovviamente si pensa di incapsulare le operazioni necessarie per reimpostare un singolo / gruppo di bit, ad esempio il valore della maschera di bit o anche operazioni guidate dall'indice di bit sarebbe utile per la manipolazione di un tale insieme di "flag".

Il struct/ class specifica supporta solo una migliore definizione dei valori enum per l'accesso. Ne più ne meno!

Modi per uscire dalla restrizione non è possibile dichiarare i metodi per enum (classi) , sia per usare una std::bitset(classe wrapper), sia un bitfieldunion .

unione tali sindacati bitfield possono avere metodi (vedi qui per le restrizioni!).

Ho un esempio di come convertire i valori della maschera di bit (come mostrato sopra) nei loro corrispondenti indici di bit, che possono essere usati std::bitsetqui: BitIndexConverter.hpp
L'ho trovato abbastanza utile per migliorare la leggibilità di alcuni decadi 'flag' basati algoritmi.


36
Esistono più casi d'uso che giustificano metodi su enum classe, ad esempio toString () e fromString (). Ogni linguaggio principale moderno (anche se non così) ha questo (es. C #, Java, Swift), ma non C ++.
Mike Lischke,

1
Speriamo per la sintassi della chiamata unificata la prossima volta ... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
sdgfsdh

4

C'è una capacità abbastanza compatibile (§) di refactificare un enum in una classe senza dover riscrivere il tuo codice, il che significa che puoi effettivamente fare ciò che ti chiedevi di fare senza troppe modifiche.

(§) come sottolinea ElementW in un commento, il codice dipendente da type_traits non funzionerà, quindi ad esempio non si può usare auto, ecc. Potrebbe esserci un modo di gestire tali cose, ma alla fine si sta convertendo un enum in una classe, ed è sempre un errore sovvertire C ++

le specifiche enum structe enum classriguardano lo scoping quindi non fanno parte di questo.

Il tuo enum originale è ad esempio 'pet' (questo è solo un esempio!).

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

(1) Lo modifichi ad es. PetEnum (in modo da nasconderlo dal tuo codice esistente).

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

(2) Aggiungete una nuova dichiarazione di classe sotto di essa (chiamata con l'enum originale)

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) Ora puoi aggiungere qualsiasi metodo di classe desideri alla tua classe di animali domestici. per esempio. un operatore di stringa

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

Ora puoi usare ad es. 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;
}

1
E ' non è pienamente compatibile: se si utilizzano i valori enum con qualsiasi tipo di tipo di detrazione in cui si prevede di ottenere un pettypename / istanza, sia essa modelli, autoo decltype, questo si rompe, come si ottiene un petEnumposto.
ElementW,

0

Potrebbe non soddisfare tutte le tue esigenze, ma con operatori non membri puoi comunque divertirti molto. Per esempio:

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

Questo permette un codice simile

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.