Herencia de clases de enumeración base

79

¿Hay un patrón en el que pueda heredar la enumeración de otra enumeración en C ++?

Algo como eso:

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


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

Respuestas:

67

Imposible. No hay herencia con enumeraciones.

En su lugar, puede usar clases con constantes con nombre.

Ejemplo:

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
fuente
¿Hay algún problema con esta solución? Por ejemplo, (no tengo un conocimiento profundo del polimorfismo) ¿Sería posible tener un vector <Colors> y usar, p = std :: find (mycolors, mycolor + length, Colors :: ORANGE) ;?
jespestana
1
@jespestana No, no usarás Colorsinstancias de clase. Solo usa los valores int en los miembros estáticos const.
jiandingzhe
Si te entiendo bien; entonces, tendría que usar un contenedor de vector <Int>. Pero aún podría escribir: p = std :: find (mycolors, mycolor + length, Colors :: ORANGE) ;. ¿derecho?
jespestana
1
@jespestana absolutamente. Además, si la búsqueda es una operación muy común, considere usar un conjunto de hash de dirección abierta o flat_set.
v.oddou
1
Re: ¿hay algún problema con esta solución? Que podría ser problemático que estos valores ya no es de un tipo distinto son. No podría escribir una función que espera un Color, como podría hacerlo para un 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
fuente
1
¡Estoy confundido! ¿Cómo se referiría entonces a los tipos de Enum en un argumento de variable o función, y cómo se aseguraría de que una función que espera Enum no reciba un EnumDeriv?
Sideshow Bob
21
Esto no funcionará. Cuando define algunas funciones int basic(EnumBase b) { return b; }y int derived(EnumDeriv d) { return d; }, esos tipos no serán convertibles a int, aunque las enumeraciones simples sí lo son. Y cuando intenta incluso como simple código como éste: cout << basic(EnumBase::One) << endl;, entonces obtendrá un error: conversion from ‘EnumBase::<anonymous enum>’ to non-scalar type ‘EnumBase’ requested. Es probable que estos problemas se solucionen agregando algunos operadores de conversión.
SasQ
10

No puede hacer eso directamente, pero puede intentar usar la solución de este artículo.

La idea principal es usar la clase de plantilla auxiliar que contiene valores de enumeración y tiene el operador de conversión de tipos. Teniendo en cuenta que el tipo subyacente de enum es intque puede usar esta clase de titular sin problemas en su código en lugar de la enumeración.

Kirill V. Lyadvinsky
fuente
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y es posible que esas personas no conozcan los motivos de su sugerencia de código.
NathanOliver
Esta es una respuesta genial; es uno de esos casos en los que "piensa en el problema de una manera diferente" y la idea de utilizar una plantilla realmente encaja perfectamente.
Den-Jason
También eche un vistazo a algunas de las soluciones para estas plantillas vis-a-vis: stackoverflow.com/questions/5871722/…
Den-Jason
5

Desafortunadamente, no es posible en C ++ 14. Espero que tengamos una característica de lenguaje de este tipo en C ++ 17. Como ya tiene algunas soluciones para su problema, no proporcionaré una solución.

Me gustaría señalar que la redacción debería ser "extensión" y no "herencia". La extensión permite más valores (ya que está saltando de 3 a 6 valores en su ejemplo) mientras que la herencia significa poner más restricciones a una clase base determinada para que el conjunto de posibilidades se reduzca. Por lo tanto, el casting potencial funcionaría exactamente de manera opuesta a la herencia. Puede convertir la clase derivada a la clase base y no viceversa con la herencia de clases. Pero al tener extensiones, "debería" poder convertir la clase base en su extensión y no al revés. Digo "debería" porque, como dije, tal característica de lenguaje todavía no existe.

Огњен Шобајић
fuente
Tenga en cuenta que extendses una palabra clave para herencia en el idioma Eiffel.
Saludos y hth. - Alf
Tiene razón porque en este caso no se respeta el principio de sustitución de Liskov. El comité no aceptará una solución que se parezca sintácticamente a la herencia debido a esto.
v.oddou
4

¿Qué tal esto? Ok, se crea una instancia para cada valor posible, pero además de eso es muy flexible. ¿Hay alguna desventaja?

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

Uso:

BaseEnum e = DerivedEnum::THREE;

if (e == DerivedEnum::THREE) {
    std::cerr << "equal" << std::endl;
}
Puñal
fuente
Las únicas desventajas que puedo ver son el mayor consumo de memoria y que necesita más líneas de código. Pero probaré tu solución.
Knitschi
También hice BaseEnum::ipúblico y BaseEnum::maxIprivado.
Knitschi
El constructor predeterminado protegido puede ser un problema cuando la enumeración debe usarse en macros o plantillas de terceros que requieren un constructor predeterminado.
Knitschi
3

Bueno, si lo define enumcon el mismo nombre en la clase derivada y lo inicia desde el último elemento correspondiente enumen la clase base, recibirá casi lo que desea: enumeración heredada. Mira este código:

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

class Inherited: public Base
{
public:
    enum ErrorType
    {
        SocketError = Base::LastItem,
        NotEnoughBandwidth,
    };
};
Haspemulator
fuente
1
mientras el código sea compilable, no podrá usarlo, ya que el compilador no podrá convertir de base :: ErrorType a Inherited :: ErrorType.
bavaza
1
@bavaza, claro, deberías usar integer en lugar de enums al pasar sus valores como parámetros.
Haspemulator
2

Como se indica en bayda, las enumeraciones no tienen (y / o no deberían) tener funcionalidad, por lo que he adoptado el siguiente enfoque para su dilema adaptando Mykola Golubyevla respuesta:

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

typedef struct : public BaseEnum
{
    enum
    {
        THREE = BaseEnum::LAST,
        FOUR,
        FIVE
    };
}DerivedEnum;
vigilancia
fuente
2
Hay pocos problemas con esta solución. Primero, está contaminando BaseEnum con LAST, que en realidad no existe más que para establecer el punto de partida para DerivedEnum. En segundo lugar, ¿qué sucede si quiero establecer explícitamente algunos valores en BaseEnum que colisionarían con los valores DerivedEnum? De todos modos, eso es probablemente lo mejor que podemos hacer hasta ahora en C ++ 14.
Огњен Шобајић
Como dije, está adaptado de un ejemplo anterior, por lo que se expresa de la manera en que está completo, no me preocupa que el póster anterior tenga problemas lógicos en su ejemplo
vigilancia
2

Puede utilizar un proyecto SuperEnum para crear enumeraciones extensibles.

/*** 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
fuente
1
Aunque es una publicación antigua, creo que merece una respuesta. Sugeriría deshacerse del constructor predeterminado y mover el constructor explícito a privado. Aún puede iniciar la variable de la forma en que lo está haciendo. Por supuesto, deberías deshacerte del const int&por un simpleint
Moia
2

Un poco hacky, pero esto es lo que se me ocurrió si estaba tratando con enumeraciones con ámbito:

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

y luego usa like:

OriginalType myOriginalType = (OriginalType)ExtendOriginalType::EXTENDED_BAR;
jsadler
fuente
2

Esta respuesta es una variante de la respuesta de Brian R. Bondy. Dado que se ha solicitado en un comentario, lo agrego como respuesta. Sin embargo, no estoy señalando si realmente vale la pena.

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

Vivir en Coliru

Moia
fuente
0

Imposible.
Pero puede definir la enumeración de forma anónima en una clase y luego agregar constantes de enumeración adicionales en las clases derivadas.

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

Uso:

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