Vererbung der Basis-Enum-Klasse

79

Gibt es ein Muster, in dem ich eine Aufzählung von einer anderen Aufzählung in C ++ erben kann?

So ähnlich:

enum eBase 
{
   one=1, two, three
};


enum eDerived: public eBase
{
   four=4, five, six
};
Stephen Kennedy
quelle

Antworten:

67

Nicht möglich. Es gibt keine Vererbung mit Aufzählungen.

Sie können stattdessen Klassen mit benannten Konstanten verwenden.

Beispiel:

class Colors
{
public:
  static const int RED = 1;
  static const int GREEN = 2;
};

class RGB : public Colors
{
  static const int BLUE = 10;
};


class FourColors : public Colors
{
public:
  static const int ORANGE = 100;
  static const int PURPLE = 101;
};
Brian R. Bondy
quelle
Gibt es ein Problem mit dieser Lösung? Zum Beispiel (ich habe kein tiefes Verständnis für Polymorphismus) Wäre es möglich, einen Vektor <Farben> zu haben und p = std :: find (mycolors, mycolor + length, Colors :: ORANGE) zu verwenden?
Jespestana
1
@jespestana Nein, Sie werden keine ColorsKlasseninstanzen verwenden. Sie verwenden nur die int-Werte in den statischen const-Elementen.
Jiandingzhe
Wenn ich dich richtig verstehe; dann müsste ich einen Vektor <Int> -Container verwenden. Aber ich könnte trotzdem schreiben: p = std :: find (mycolors, mycolor + length, Colors :: ORANGE);. Recht?
Jespestana
1
@ Jespestana absolut. Auch wenn die Suche eine sehr häufige Operation ist, sollten Sie ein flat_set oder ein Hash-Set für offene Adressen verwenden.
v.oddou
1
Betreff: Gibt es ein Problem mit dieser Lösung? Es könnte problematisch sein, dass diese Werte nicht mehr von einem bestimmten Typ sind. Sie konnten keine Funktion schreiben, die eine erwartet Color, wie Sie es für eine könnten enum.
Drew Dormann
93
#include <iostream>
#include <ostream>

class Enum
{
public:
    enum
    {
        One = 1,
        Two,
        Last
    };
};

class EnumDeriv : public Enum
{
public:
    enum
    {
        Three = Enum::Last,
        Four,
        Five
    };
};

int main()
{
    std::cout << EnumDeriv::One << std::endl;
    std::cout << EnumDeriv::Four << std::endl;
    return 0;
}
Mykola Golubyev
quelle
1
Ich bin verwirrt! Wie würden Sie dann in einer Variablen oder einem Funktionsargument auf die Enum-Typen verweisen und wie würden Sie sicherstellen, dass einer Funktion, die Enum erwartet, kein EnumDeriv zugewiesen wurde?
Sideshow Bob
21
Das wird nicht funktionieren. Wenn Sie einige Funktionen int basic(EnumBase b) { return b; }und definieren int derived(EnumDeriv d) { return d; }, können diese Typen nicht konvertiert werden int, obwohl dies einfache Aufzählungen sind. Und wenn Sie selbst so einfachen Code wie diesen ausprobieren cout << basic(EnumBase::One) << endl;:, erhalten Sie eine Fehlermeldung : conversion from ‘EnumBase::<anonymous enum>’ to non-scalar type ‘EnumBase’ requested. Diese Probleme können wahrscheinlich durch Hinzufügen einiger Konvertierungsoperatoren behoben werden.
SasQ
10

Sie können dies nicht direkt tun, aber Sie könnten versuchen, die Lösung aus diesem Artikel zu verwenden.

Die Hauptidee besteht darin, die Hilfsvorlagenklasse zu verwenden, die Enum-Werte enthält und den Operator type cast hat. Wenn man bedenkt, dass der zugrunde liegende Typ für die Aufzählung ist int, können Sie diese Halterklasse nahtlos in Ihrem Code anstelle der Aufzählung verwenden.

Kirill V. Lyadvinsky
quelle
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich dabei, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage in Zukunft für Leser beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
NathanOliver
Dies ist eine großartige Antwort. Es ist eine dieser Instanzen von "Denken Sie anders über das Problem nach" und die Idee, eine Vorlage zu verwenden, passt wirklich zur Rechnung.
Den-Jason
Schauen Sie sich auch einige der Lösungen für diese Vorlagen an: stackoverflow.com/questions/5871722/…
Den-Jason
5

Leider ist dies in C ++ 14 nicht möglich. Ich hoffe, wir werden eine solche Sprachfunktion in C ++ 17 haben. Da Sie bereits einige Problemumgehungen für Ihr Problem haben, werde ich keine Lösung anbieten.

Ich möchte darauf hinweisen, dass der Wortlaut "Erweiterung" und nicht "Vererbung" sein sollte. Die Erweiterung ermöglicht mehr Werte (da Sie in Ihrem Beispiel von 3 auf 6 Werte springen), während Vererbung bedeutet, dass einer bestimmten Basisklasse mehr Einschränkungen auferlegt werden, sodass die Anzahl der Möglichkeiten abnimmt. Daher würde potenzielles Casting genau entgegengesetzt zur Vererbung funktionieren. Sie können abgeleitete Klassen in die Basisklasse umwandeln und nicht umgekehrt mit Klassenvererbung. Wenn Sie jedoch Erweiterungen haben, sollten Sie in der Lage sein, die Basisklasse in ihre Erweiterung umzuwandeln und nicht umgekehrt. Ich sage "sollte", weil, wie gesagt, eine solche Sprachfunktion immer noch nicht existiert.

Огњен Шобајић
quelle
Beachten Sie, dass dies extendsein Schlüsselwort für die Vererbung in der Eiffel-Sprache ist.
Prost und hth. - Alf
Sie haben Recht, denn in diesem Fall wird das Liskov-Substitutionsprinzip nicht eingehalten. Das Komitee akzeptiert aus diesem Grund keine Lösung, die syntaktisch wie Vererbung aussieht.
v.oddou
4

Wie wäre es damit? Ok, für jeden möglichen Wert wird eine Instanz erstellt, aber außerdem ist sie sehr flexibel. Gibt es Nachteile?

.h:

class BaseEnum
{
public:
  static const BaseEnum ONE;
  static const BaseEnum TWO;

  bool operator==(const BaseEnum& other);

protected:
  BaseEnum() : i(maxI++) {}
  const int i;
  static int maxI;
};

class DerivedEnum : public BaseEnum
{
public:
  static const DerivedEnum THREE;
};

.cpp:

int BaseEnum::maxI = 0;

bool BaseEnum::operator==(const BaseEnum& other) {
  return i == other.i;
}

const BaseEnum BaseEnum::ONE;
const BaseEnum BaseEnum::TWO;
const DerivedEnum DerivedEnum::THREE;

Verwendung:

BaseEnum e = DerivedEnum::THREE;

if (e == DerivedEnum::THREE) {
    std::cerr << "equal" << std::endl;
}
Dolch
quelle
Die einzigen Nachteile, die ich sehen kann, sind der höhere Speicherverbrauch und der Bedarf an mehr Codezeilen. Aber ich werde Ihre Lösung versuchen.
Knitschi
Ich habe auch BaseEnum::iöffentlich und BaseEnum::maxIprivat gemacht.
Knitschi
Der geschützte Standardkonstruktor kann ein Problem sein, wenn die Aufzählung in Makros oder Vorlagen von Drittanbietern verwendet werden muss, für die ein Standardkonstruktor erforderlich ist.
Knitschi
3

Wenn Sie enumin der abgeleiteten Klasse denselben Namen definieren und ihn vom letzten Korrespondenzelement enumin der Basisklasse starten , erhalten Sie fast das, was Sie möchten - geerbte Aufzählung. Schauen Sie sich diesen Code an:

class Base
{
public:
    enum ErrorType
    {
        GeneralError,
        NoMemory,
        FileNotFound,
        LastItem,
    };
};

class Inherited: public Base
{
public:
    enum ErrorType
    {
        SocketError = Base::LastItem,
        NotEnoughBandwidth,
    };
};
Haspemulator
quelle
1
Während der Code kompilierbar ist, können Sie ihn nicht verwenden, da der Compiler nicht von base :: ErrorType nach Inherited :: ErrorType konvertieren kann.
Bayaza
1
@bavaza, sicher, Sie sollten eine Ganzzahl anstelle von Aufzählungen verwenden, wenn Sie deren Werte als Parameter übergeben.
Haspemulator
2

Wie von angegeben bayda, haben Enums keine Funktionalität (und / oder sollten es auch nicht), daher habe ich den folgenden Ansatz für Ihr Dilemma gewählt, indem ich die Mykola GolubyevAntwort angepasst habe :

typedef struct
{
    enum
    {
        ONE = 1,
        TWO,
        LAST
    };
}BaseEnum;

typedef struct : public BaseEnum
{
    enum
    {
        THREE = BaseEnum::LAST,
        FOUR,
        FIVE
    };
}DerivedEnum;
Wachsamkeit
quelle
2
Es gibt nur wenige Probleme mit dieser Lösung. Erstens verschmutzen Sie BaseEnum mit LAST, das nur existiert, um den Startpunkt für DerivedEnum festzulegen. Zweitens, was ist, wenn ich einige Werte in BaseEnum explizit festlegen möchte, die mit DerivedEnum-Werten kollidieren würden? Das ist wahrscheinlich das Beste, was wir bisher in C ++ 14 tun können.
18гњен Шобајић
Wie ich bereits sagte, wurde es aus einem vorherigen Beispiel übernommen, so dass es der Vollständigkeit
Wachsamkeit
2

Sie können ein Projekt SuperEnum verwenden , um erweiterbare Aufzählungen zu erstellen.

/*** my_enum.h ***/
class MyEnum: public SuperEnum<MyEnum>
{
public:
    MyEnum() {}
    explicit MyEnum(const int &value): SuperEnum(value) {}

    static const MyEnum element1;
    static const MyEnum element2;
    static const MyEnum element3;
};

/*** my_enum.cpp ***/
const MyEnum MyEnum::element1(1);
const MyEnum MyEnum::element2;
const MyEnum MyEnum::element3;

/*** my_enum2.h ***/
class MyEnum2: public MyEnum
{
public:
    MyEnum2() {}
    explicit MyEnum2(const int &value): MyEnum(value) {}

    static const MyEnum2 element4;
    static const MyEnum2 element5;
};

/*** my_enum2.cpp ***/
const MyEnum2 MyEnum2::element4;
const MyEnum2 MyEnum2::element5;

/*** main.cpp ***/
std::cout << MyEnum2::element3;
// Output: 3
Dmitry Bravikov
quelle
1
Obwohl es sich um einen alten Beitrag handelt, denke ich, dass dies eine Antwort verdient. Ich würde vorschlagen, den Standardkonstruktor loszuwerden und den expliziten Konstruktor auf privat zu verschieben. Sie können die Variable weiterhin so initiieren, wie Sie es tun. Natürlich sollten Sie die const int&für eine einfacheint
Moia
2

Ein bisschen hacky, aber das ist es, was ich mir ausgedacht habe, wenn ich mich mit Enums befasst habe:

enum class OriginalType {
   FOO,  // 0
   BAR   // 1
   END   // 2
};

enum class ExtendOriginalType : std::underlying_type_t<OriginalType> {
   EXTENDED_FOO = static_cast<std::underlying_type_t<OriginalType>>
                                           (OriginalType::END), // 2
   EXTENDED_BAR  // 3
};

und dann benutze wie:

OriginalType myOriginalType = (OriginalType)ExtendOriginalType::EXTENDED_BAR;
jsadler
quelle
0

Unmöglich.
Sie können die Aufzählung jedoch anonym in einer Klasse definieren und dann in abgeleiteten Klassen zusätzliche Aufzählungskonstanten hinzufügen.

Bayda
quelle
0

Diese Antwort ist eine Variante der Antwort von Brian R. Bondy. Da in einem Kommentar angefordert wurde, füge ich ihn als Antwort hinzu. Ich weise nicht darauf hin, ob es sich wirklich lohnt.

#include <iostream>

class Colors
{
public:
    static Colors RED;
    static Colors GREEN;

    operator int(){ return value; }
    operator int() const{ return value; }

protected:
    Colors(int v) : value{v}{} 

private:
    int value;
};

Colors Colors::RED{1};
Colors Colors::GREEN{2};

class RGB : public Colors
{
public:
    static RGB BLUE;

private:
    RGB(int v) : Colors(v){}
};

RGB RGB::BLUE{10};

int main ()
{
  std::cout << Colors::RED << " " << RGB::RED << std::endl;
}

Lebe in Coliru

Moia
quelle
-2
enum xx {
   ONE = 1,
   TWO,
   xx_Done
};

enum yy {
   THREE = xx_Done,
   FOUR,
};

typedef int myenum;

static map<myenum,string>& mymap() {
   static map<myenum,string> statmap;
   statmap[ONE] = "One";
   statmap[TWO] = "Two";
   statmap[THREE] = "Three";
   statmap[FOUR] = "Four";
   return statmap;
}

Verwendung:

std::string s1 = mamap()[ONE];
std::string s4 = mymap()[FOUR];
Michal Cohen
quelle