Наследование класса базового перечисления

79

Есть ли шаблон, по которому я могу наследовать перечисление от другого перечисления в C ++?

Что-то такое:

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


enum eDerived: public eBase
{
   four=4, five, six
};
Стивен Кеннеди
источник

Ответы:

67

Невозможно. С перечислениями наследования нет.

Вместо этого вы можете использовать классы с именами const ints.

Пример:

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;
};
Брайан Р. Бонди
источник
Есть ли проблема с этим решением? Например, (у меня нет глубокого понимания полиморфизма) Можно ли иметь вектор <Colors> и использовать p = std :: find (mycolors, mycolor + length, Colors :: ORANGE) ;?
jespestana
1
@jespestana Нет, вы не будете использовать Colorsэкземпляры классов. Вы используете только значения int в статических константных членах.
jiandingzhe
Если я правильно вас понимаю; тогда мне пришлось бы использовать контейнер vector <Int>. Но я все равно мог бы написать: p = std :: find (mycolors, mycolor + length, Colors :: ORANGE) ;. правильно?
jespestana
1
@jespestana абсолютно. также, если поиск - очень распространенная операция, рассмотрите возможность использования flat_set или хеш-набора открытых адресов.
v.oddou
1
Re: Есть ли проблемы с этим решением? Может возникнуть проблема, если эти значения больше не относятся к определенному типу. Вы не могли бы написать функцию, которая ожидает Color, как вы могли бы для enum.
Дрю
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;
}
Николай Голубев
источник
1
Я не совсем понимаю! Как бы вы затем сослались на типы Enum в аргументе переменной или функции и как обеспечить, чтобы функция, ожидающая Enum, не получила EnumDeriv?
Sideshow Боб
21
Это не сработает. Когда вы определяете некоторые функции int basic(EnumBase b) { return b; }и int derived(EnumDeriv d) { return d; }, эти типы не будут преобразованы в int, хотя простые перечисления могут. И когда вы пытаетесь даже такой простой код , как этот: cout << basic(EnumBase::One) << endl;, то вы получите сообщение об ошибке: conversion from ‘EnumBase::<anonymous enum>’ to non-scalar type ‘EnumBase’ requested. Эти проблемы, вероятно, можно решить, добавив некоторые операторы преобразования.
SasQ
10

Вы не можете сделать это напрямую, но можете попробовать использовать решение из этой статьи.

Основная идея - использовать вспомогательный шаблонный класс, который содержит значения перечисления и имеет оператор приведения типа. Учитывая, что базовый тип для enum - intвы можете легко использовать этот класс-держатель в своем коде вместо enum.

Кирилл Васильевич Лядвинский
источник
Хотя этот фрагмент кода может решить вопрос, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин вашего предложения кода.
Натан Оливер,
Это отличный ответ; это один из тех случаев, когда «подумайте о проблеме по-другому», и идея использования шаблона действительно отвечает всем требованиям.
Ден-Джейсон
Также ознакомьтесь с некоторыми решениями для этих шаблонов vis-a-vis: stackoverflow.com/questions/5871722/…
Ден-Джейсон
5

К сожалению, в C ++ 14 это невозможно. Я надеюсь, что у нас будет такая языковая функция в C ++ 17. Поскольку у вас уже есть несколько способов решения вашей проблемы, я не буду предлагать решение.

Хочу отметить, что формулировка должна быть «расширение», а не «наследование». Расширение позволяет использовать больше значений (поскольку в вашем примере вы перескакиваете с 3 на 6 значений), тогда как наследование означает наложение большего количества ограничений на данный базовый класс, поэтому набор возможностей сокращается. Следовательно, потенциальное приведение будет работать прямо противоположно наследованию. Вы можете привести производный класс к базовому классу, а не наоборот с наследованием классов. Но при наличии расширений вы «должны» иметь возможность преобразовывать базовый класс в его расширение, а не наоборот. Я говорю «следует», потому что, как я уже сказал, такой языковой функции все еще не существует.

Огњен Шобајић
источник
Обратите внимание, что extendsэто ключевое слово для наследования в языке Eiffel.
Приветствия и hth. - Альф,
Вы правы, потому что в этом случае не соблюдается принцип замещения Лискова. По этой причине комитет не примет решение, которое синтаксически выглядит как наследование.
v.oddou
4

Как насчет этого? Хорошо, экземпляр создается для всех возможных значений, но, кроме того, он очень гибкий. Есть ли минусы?

.час:

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;

Применение:

BaseEnum e = DerivedEnum::THREE;

if (e == DerivedEnum::THREE) {
    std::cerr << "equal" << std::endl;
}
Дирк
источник
Единственные недостатки, которые я вижу, - это более высокий расход памяти и необходимость в большем количестве строк кода. Но я попробую ваше решение.
Knitschi
Я тоже сделал BaseEnum::iпубличный и BaseEnum::maxIприватный.
Knitschi
Защищенный конструктор по умолчанию может быть проблемой, когда перечисление необходимо использовать в сторонних макросах или шаблонах, для которых требуется конструктор по умолчанию.
Knitschi
3

Что ж, если вы определите enumс тем же именем в производном классе и запустите его с последнего элемента корреспондента enumв базовом классе, вы получите почти то, что хотите - унаследованное перечисление. Взгляните на этот код:

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

class Inherited: public Base
{
public:
    enum ErrorType
    {
        SocketError = Base::LastItem,
        NotEnoughBandwidth,
    };
};
Haspemulator
источник
1
пока код компилируемый, вы не сможете его использовать, так как компилятор не сможет преобразовать base :: ErrorType в Inherited :: ErrorType.
bavaza
1
@bavaza, конечно, вы должны использовать целые числа вместо перечислений при передаче их значений в качестве параметров.
Haspemulator 03
2

Как указано bayda, перечисления не имеют (и / или не должны) иметь функциональность, поэтому я применил следующий подход к вашему затруднительному положению, адаптировав Mykola Golubyevответ:

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

typedef struct : public BaseEnum
{
    enum
    {
        THREE = BaseEnum::LAST,
        FOUR,
        FIVE
    };
}DerivedEnum;
бдительность
источник
2
Есть несколько проблем с этим решением. Во-первых, вы загрязняете BaseEnum LAST, который на самом деле не существует, кроме как установить отправную точку для DerivedEnum. Во-вторых, что, если я хочу явно установить некоторые значения в BaseEnum, которые будут конфликтовать со значениями DerivedEnum? В любом случае, это, вероятно, лучшее, что мы можем сделать в C ++ 14.
Огњен Шобајић
Как я уже сказал, он адаптирован из предыдущего примера, поэтому он выражен так, как он есть для полноты, меня не беспокоит, что предыдущий плакат имеет логические проблемы в их примере
бдительность
2

Вы можете использовать проект SuperEnum для создания расширяемых перечислений.

/*** 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
Дмитрий Бравиков
источник
1
Хотя это старый пост, я считаю, что он заслуживает ответа. Я бы предложил избавиться от конструктора по умолчанию и переместить явный конструктор в частный. Вы по-прежнему можете инициализировать переменную, как вы это делаете. Конечно, вы должны избавиться от const int&простогоint
Moia
2

Вроде хакерство, но вот что я придумал, имея дело с перечислениями с областью видимости:

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

а затем используйте как:

OriginalType myOriginalType = (OriginalType)ExtendOriginalType::EXTENDED_BAR;
jsadler
источник
2

Этот ответ является вариантом ответа Брайана Р. Бонди. Поскольку был запрошен в комментарии, я добавляю его в качестве ответа. Я не говорю о том, действительно ли это того стоит.

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

Жить в Колиру

Моя
источник
0

Невозможно.
Но вы можете определить перечисление анонимно в классе, а затем добавить дополнительные константы перечисления в производные классы.

байда
источник
-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;
}

Применение:

std::string s1 = mamap()[ONE];
std::string s4 = mymap()[FOUR];
Михал Коэн
источник