Dziedziczenie podstawowej klasy wyliczeniowej

79

Czy istnieje wzorzec, w którym mogę dziedziczyć wyliczenie z innego wyliczenia w C ++?

Coś w tym stylu:

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


enum eDerived: public eBase
{
   four=4, five, six
};
Stephen Kennedy
źródło

Odpowiedzi:

67

Niemożliwe. Nie ma dziedziczenia z wyliczeniami.

Zamiast tego możesz używać klas z nazwanymi stałymi intami.

Przykład:

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
źródło
Czy jest jakiś problem z tym rozwiązaniem? Na przykład (nie mam głębokiego zrozumienia polimorfizmu) Czy byłoby możliwe mieć wektor <Colors> i użyć, p = std :: find (mycolors, mycolor + length, Colors :: ORANGE) ;?
jespestana
1
@jespestana Nie, nie będziesz używać Colorsinstancji klas. Używasz tylko wartości int w statycznych składowych stałych.
jiandingzhe
Jeśli dobrze cię rozumiem; wtedy musiałbym użyć kontenera wektorowego <Int>. Ale nadal byłbym w stanie napisać: p = std :: find (mykolory, mykolor + długość, kolory :: POMARAŃCZOWY) ;. dobrze?
jespestana
1
@jespestana absolutnie. również jeśli wyszukiwanie jest bardzo powszechną operacją, rozważ użycie zestawu flat_set lub zestawu skrótów otwartego adresu.
v.oddou
1
Re: Czy jest jakiś problem z tym rozwiązaniem? Może być problematyczne, że te wartości nie są już odrębnym typem. Nie można napisać funkcji, która oczekuje a Color, tak jak w przypadku 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;
}
Mykoła Golubyev
źródło
1
Jestem zmieszany! W jaki sposób można by wtedy odnieść się do typów wyliczenia w zmiennej lub argumencie funkcji i jak zapewnić, że funkcja oczekująca wyliczenia nie otrzymała wartości EnumDeriv?
Sideshow Bob
21
To nie zadziała. Podczas definiowania niektórych funkcji int basic(EnumBase b) { return b; }i int derived(EnumDeriv d) { return d; }te typy nie będą konwertowane na int, chociaż zwykłe wyliczenia są. I podczas próby nawet takiego kodu proste jak ten: cout << basic(EnumBase::One) << endl;Pokochasz więc pojawia się błąd: conversion from ‘EnumBase::<anonymous enum>’ to non-scalar type ‘EnumBase’ requested. Prawdopodobnie te problemy można rozwiązać poprzez dodanie kilku operatorów konwersji.
SasQ
10

Nie możesz tego zrobić bezpośrednio, ale możesz spróbować użyć rozwiązania z tego artykułu.

Głównym pomysłem jest użycie pomocniczej klasy szablonu, która przechowuje wartości wyliczenia i ma operator rzutowania typu. Biorąc pod uwagę, że podstawowym typem wyliczenia jest intbezproblemowe użycie tej klasy posiadacza w kodzie zamiast wyliczenia.

Kirill V. Lyadvinsky
źródło
Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
NathanOliver
To świetna odpowiedź; jest to jeden z tych przypadków, w których „pomyśleć o problemie w inny sposób”, a pomysł użycia szablonu naprawdę pasuje do rachunku.
Den-Jason,
Również spojrzeć na niektóre z rozwiązań tego vis-a-vis szablony: stackoverflow.com/questions/5871722/...
Den-Jason
5

Niestety nie jest to możliwe w C ++ 14. Mam nadzieję, że taka funkcja języka będzie dostępna w C ++ 17. Ponieważ masz już kilka obejść swojego problemu, nie przedstawię rozwiązania.

Chciałbym zaznaczyć, że powinno brzmieć „rozszerzenie”, a nie „dziedziczenie”. Rozszerzenie pozwala na więcej wartości (jak w przykładzie przeskakujesz z 3 do 6 wartości), podczas gdy dziedziczenie oznacza nakładanie większej liczby ograniczeń na daną klasę bazową, więc zestaw możliwości się kurczy. Dlatego potencjalne rzutowanie działałoby dokładnie odwrotnie niż dziedziczenie. Możesz rzutować klasę pochodną na klasę bazową, a nie na odwrót z dziedziczeniem klas. Ale mając rozszerzenia, "powinieneś" być w stanie rzutować klasę bazową na jej rozszerzenie, a nie odwrotnie. Mówię „powinienem”, ponieważ, jak powiedziałem, taka funkcja języka nadal nie istnieje.

Огњен Шобајић
źródło
Należy pamiętać, że extendsjest to słowo kluczowe określające dziedziczenie w języku Eiffla.
Pozdrawiam i hth. - Alf
Masz rację, ponieważ w tym przypadku Zasada Zastępstwa Liskova nie jest przestrzegana. Z tego powodu komisja nie zaakceptuje rozwiązania, które wygląda jak dziedziczenie.
v.oddou
4

Co powiesz na to? Ok, instancja jest tworzona dla każdej możliwej wartości, ale poza tym jest bardzo elastyczna. Czy są jakieś wady?

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

Stosowanie:

BaseEnum e = DerivedEnum::THREE;

if (e == DerivedEnum::THREE) {
    std::cerr << "equal" << std::endl;
}
Sztylet
źródło
Jedyne wady, które widzę, to większe zużycie pamięci i to, że potrzebuje więcej linii kodu. Ale spróbuję twojego rozwiązania.
Knitschi
Zrobiłem również BaseEnum::ipubliczne i BaseEnum::maxIprywatne.
Knitschi
Chroniony konstruktor domyślny może stanowić problem, gdy wyliczenie musi być używane w makrach lub szablonach innych firm, które wymagają domyślnego konstruktora.
Knitschi
3

Cóż, jeśli zdefiniujesz enumtę samą nazwę w klasie pochodnej i zaczniesz ją od ostatniego elementu korespondenta enumw klasie bazowej, otrzymasz prawie to, czego chcesz - dziedziczone wyliczenie. Spójrz na ten kod:

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

class Inherited: public Base
{
public:
    enum ErrorType
    {
        SocketError = Base::LastItem,
        NotEnoughBandwidth,
    };
};
Haspemulator
źródło
1
podczas gdy kod jest kompilowalny, nie będziesz mógł go używać, ponieważ kompilator nie będzie w stanie przekonwertować z base :: ErrorType na Inherited :: ErrorType.
bavaza
1
@bavaza, jasne, powinieneś używać liczb całkowitych zamiast wyliczeń podczas przekazywania ich wartości jako parametrów.
Haspemulator
2

Jak stwierdzono bayda, wyliczenia nie mają (i / lub nie powinny) mieć funkcjonalności, więc zastosowałem następujące podejście do twojego dylematu, dostosowując Mykola Golubyevodpowiedź:

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

typedef struct : public BaseEnum
{
    enum
    {
        THREE = BaseEnum::LAST,
        FOUR,
        FIVE
    };
}DerivedEnum;
czujność
źródło
2
Z tym rozwiązaniem jest kilka problemów. Po pierwsze, zanieczyszczasz BaseEnum przez LAST, który tak naprawdę nie istnieje poza ustawieniem punktu początkowego dla DerivedEnum. Po drugie, co jeśli chcę jawnie ustawić niektóre wartości w BaseEnum, które kolidowałyby z wartościami DerivedEnum? W każdym razie to chyba najlepsze, co możemy zrobić, jeśli chodzi o C ++ 14.
Огњен Шобајић
Tak jak powiedziałem, jest to zaadaptowane z poprzedniego przykładu, więc jest wyrażone w sposób kompletny, nie martwię się, że poprzedni plakat ma na swoim przykładzie problemy logiczne
czujność
2

Możesz użyć projektu SuperEnum do tworzenia rozszerzalnych wyliczeń.

/*** 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
Dmitrij Bravikov
źródło
1
Chociaż to stary post, czuję, że zasługuje na odpowiedź. Sugerowałbym pozbycie się domyślnego konstruktora i przeniesienie jawnego konstruktora do prywatnego. Nadal możesz zainicjować zmienną w sposób, w jaki robisz. Oczywiście powinieneś pozbyć const int&się prostegoint
Moia
2

Trochę hacky, ale oto, co wymyśliłem, jeśli mam do czynienia z wyliczeniami o określonym zakresie:

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

a następnie użyj:

OriginalType myOriginalType = (OriginalType)ExtendOriginalType::EXTENDED_BAR;
jsadler
źródło
2

Ta odpowiedź jest wariantem odpowiedzi Briana R. Bondy'ego. Ponieważ został o to poproszony w komentarzu, dodaję to jako odpowiedź. Nie wskazuję jednak, czy naprawdę warto.

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

Mieszkaj w Coliru

Moia
źródło
0

Niemożliwy.
Ale można zdefiniować wyliczenie anonimowo w klasie, a następnie dodać dodatkowe stałe wyliczenia w klasach pochodnych.

bayda
źródło
-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;
}

Stosowanie:

std::string s1 = mamap()[ONE];
std::string s4 = mymap()[FOUR];
Michał Cohen
źródło