Kann eine C ++ - Enum-Klasse Methoden haben?

144

Ich habe eine Enum-Klasse mit zwei Werten und möchte eine Methode erstellen, die einen Wert empfängt und den anderen zurückgibt. Ich möchte auch die Typensicherheit gewährleisten (deshalb verwende ich die Aufzählungsklasse anstelle von Aufzählungen).

http://www.cplusplus.com/doc/tutorial/other_data_types/ erwähnt nichts über Methoden Ich hatte jedoch den Eindruck, dass jede Art von Klasse Methoden haben kann.

Oktavian
quelle
4
Nein, ich kann nicht. Siehe hier .
Juanchopanza
@octavian Beachten Sie meine Antwort und überdenken Sie bitte Ihre Anwendungsfälle!
πάντα ῥεῖ
@ πάνταῥεῖ du hast vollkommen recht, ich habe enum gelesen aber dachte union, hat den kommentar getötet.
Eugen Constantin Dinca
@octavian Sind Sie aufgefordert werden, selbst für einen bestimmten Anwendungsfall überhaupt, oder wollten Sie nur die Standards Beschränkungen haben c ++ 11 enum class/struct bestätigt?
πάντα ῥεῖ
Ich hatte eine Verwendung im Sinn ... und dies war das grundlegende Problem
Octavian

Antworten:

118

Nein, das können sie nicht.

Ich kann verstehen, dass der enum classTeil für stark typisierte Aufzählungen in C ++ 11 möglicherweise impliziert, dass Sie enumauch classMerkmale haben, aber das ist nicht der Fall. Meine Vermutung ist, dass die Auswahl der Schlüsselwörter von dem Muster inspiriert wurde, das wir vor C ++ 11 verwendet haben, um Aufzählungen mit Gültigkeitsbereich zu erhalten:

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

Das ist jedoch nur Syntax. Auch hier enum classist kein class.

Stefano Sanfilippo
quelle
88
Auf ## C ++ wurde mir gesagt, dass "c ++ so verwirrend und fachkundig wie möglich sein soll" . Natürlich ist es ein Witz, aber Sie bekommen die Idee :)
Stefano Sanfilippo
4
A unionist nicht das, was John Doe auch als Klasse betrachten würde. Sie können jedoch Mitgliedsfunktionen haben. Und Klassen sind für Mitgliedsfunktionen wirklich nicht obligatorisch. Die Verwendung eines Bezeichners wie valueoder this, so etwas wie enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; }(hier auch zulässig ;), kann genauso sinnvoll sein wie andere Arten von Funktionen.
Sebastian Mach
84

Obwohl die Antwort "Sie können nicht" technisch korrekt ist, glaube ich, dass Sie möglicherweise das gewünschte Verhalten mit der folgenden Idee erreichen können:

Ich stelle mir vor, dass Sie etwas schreiben möchten wie:

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

Und Sie hatten gehofft, dass der Code ungefähr so ​​aussieht:

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

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

...

Aber natürlich funktioniert es nicht, weil Enums keine Methoden haben können (und 'das' bedeutet im obigen Kontext nichts)

Wenn Sie jedoch die Idee einer normalen Klasse verwenden, die eine Aufzählung außerhalb der Klasse und eine einzelne Elementvariable enthält, die einen Wert dieses Typs enthält, können Sie der gewünschten Syntax / Verhalten / Typensicherheit sehr nahe kommen. dh:

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

Jetzt können Sie schreiben:

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

Und der Compiler verhindert Dinge wie:

Fruit f = 1;  // Compile time error.

Sie können leicht Methoden hinzufügen, die:

Fruit f("Apple");

und

f.ToString();

kann unterstützt werden.

jtlim
quelle
1
Sollte nicht auch IsYellow (), operator == ,! = Als constexpr markiert sein?
Jarek C
Ich
erhalte
18

Eine mögliche Antwort ist die Konzentration auf die Beschreibung der Frage anstelle des Titels

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
quelle
4

Wie in der anderen Antwort erwähnt , nein. Auch enum classist keine Klasse.


Normalerweise ist die Notwendigkeit haben Methoden für eine enumergibt sich aus dem Grund , dass es nicht eine regelmäßige (nur Inkrementieren) Enum, aber irgendwie bitweise Definition von Werten maskiert werden oder müssen andere Bit-Rechenoperationen:

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;

Offensichtlich denkt man daran, die notwendigen Operationen zum Zurücksetzen / Zurücksetzen einer einzelnen / Gruppe von Bits zu kapseln, beispielsweise durch einen Bitmaskenwert oder sogar durch bitindexgesteuerte Operationen, die zur Manipulation eines solchen Satzes von "Flags" nützlich wären.

Das struct/ class specation unterstützt nur ein besseres Scoping der Enum-Werte für den Zugriff. Nicht mehr und nicht weniger!

Um aus der Einschränkung herauszukommen, können Sie keine Methoden für enum (Klassen) deklarieren , indem Sie entweder eine std::bitset(Wrapper-Klasse) oder ein Bitfeld verwendenunion .

unions, und solche Bitfeld-Gewerkschaften können Methoden haben (siehe hier für die Einschränkungen!).

Ich habe ein Beispiel, wie Bitmaskenwerte (wie oben gezeigt) in ihre entsprechenden Bitindizes konvertiert werden, die hier verwendet std::bitsetwerden können: BitIndexConverter.hpp
Ich fand dies ziemlich nützlich, um die Lesbarkeit einiger ' Flag'-Entscheidungen zu verbessern Algorithmen.

πάντα ῥεῖ
quelle
36
Es gibt weitere Anwendungsfälle, die Methoden für Enum Classe rechtfertigen, z. B. toString () und fromString (). Jede (auch nicht so) moderne Hauptsprache hat dies (z. B. C #, Java, Swift), nur nicht C ++.
Mike Lischke
1
Hoffen wir auf eine einheitliche
Anrufsyntax
4

Es gibt eine ziemlich kompatible Fähigkeit (§), eine Aufzählung in eine Klasse umzuwandeln, ohne Ihren Code neu schreiben zu müssen, was bedeutet, dass Sie effektiv das tun können , was Sie wollten, ohne zu viel zu bearbeiten.

(§) Wie ElementW in einem Kommentar hervorhebt , funktioniert type_traits- abhängiger Code nicht, z. B. kann man nicht auto usw. verwenden. Es kann eine Möglichkeit geben, mit solchen Dingen umzugehen , aber am Ende konvertiert man eine Aufzählung in eine Klasse. und es ist immer ein Fehler, C ++ zu untergraben

Die enum structund enum classSpezifikationen beziehen sich auf das Scoping, sind also nicht Teil davon.

Ihre ursprüngliche Aufzählung ist zB "Haustier" (dies ist nur ein Beispiel!).

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

(1) Sie ändern dies in zB petEnum (um es vor Ihrem vorhandenen Code zu verbergen).

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

(2) Sie fügen darunter eine neue Klassendeklaration hinzu (benannt mit der ursprünglichen Aufzählung).

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) Sie können Ihrer Haustierklasse jetzt beliebige Klassenmethoden hinzufügen. z.B. ein String-Operator

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

Jetzt können Sie zB std :: cout verwenden ...

int main() {
    pet myPet = rabbit;
    if(myPet != fish) {
        cout << "No splashing! ";
    }
    std::cout << "I have a " << std::string(myPet) << std::endl;
    return 0;
}
Konchog
quelle
1
Es ist nicht vollständig kompatibel: Wenn Sie die Aufzählungswerte mit einer Art petTypabzug verwenden , bei dem ein Typname / eine Instanz erwartet wird , seien es Vorlagen autooder decltypedies bricht ab, wenn Sie petEnumstattdessen eine erhalten.
ElementW
0

Es erfüllt möglicherweise nicht alle Ihre Anforderungen, aber mit Betreibern, die keine Mitglieder sind, können Sie trotzdem viel Spaß haben. Beispielsweise:

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

Dies ermöglicht Code wie

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
quelle