Как добиться повторного использования кода с помощью вложенных перечислений

Я пытаюсь построить класс так, чтобы его подклассы имели C ++ 11 enum class атрибут со связанным установщиком / получателем:

class Base {
public:
enum class MyEnum {
A,
B
} my_enum;

Base() : my_enum(MyEnum::A) {}

MyEnum get() { return my_enum; }
void set(const MyEnum &ME) { my_enum = ME; }
};

class Derived : public Base {
public:
Derived(): Base() {
my_enum = MyEnum::B;
}
};

int main(int argc, char *argv[])
{
Derived Deriv;

// No problems!
Deriv.get() == Derived::MyEnum::B;

return 0;
}

Все идет нормально!

Однако я хотел бы, чтобы производные классы могли переопределять MyEnum класс перечисления, при этом нет необходимости постоянно переопределять атрибут setter / getter /:

// Base as before

class Derived : public Base {
public:

enum class MyEnum {
C,
D
}; // intention: to override Base's "my_enum" attribute

Derived(): Base() {
my_enum = MyEnum::C;
// ERROR: cannot convert 'Derived::MyEnum' to 'Base::MyEnum'
}
};

int main(int argc, char *argv[])
{
Derived Deriv;

// ERROR: no match for 'operator==' for types 'Base::MyEnum' and 'Derived::MyEnum'
Deriv.get() == Derived::MyEnum::C;

return 0;
}

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

Желательно только через наследование (или, скорее, функциональность должна быть доступна для классов Derived () исключительно благодаря процессу наследования от Base ()).

Какие-либо предложения?

1

Решение

Вы могли бы сделать Base шаблон, параметризованный типом enum и использующий класс признаков для предоставления типа enum «по умолчанию», который может быть специализирован для производных типов.

template<typename T>
struct MyEnumTraits
{
enum class type {
A,
B
};

static const type default_value = type::A;
};

template<typename T = void>
class Base {
public:

typedef typename MyEnumTraits<T>::type MyEnum;
MyEnum my_enum;

Base() : my_enum(MyEnumTraits<T>::default_value) {}

MyEnum get() { return my_enum; }
void set(const MyEnum &ME) { my_enum = ME; }
};

class Derived;

template<>
struct MyEnumTraits<Derived>
{
enum class type {
C,
D
};
static const type default_value = type::C;
};

class Derived : public Base<Derived> {
// ...

Но теперь разные производные типы будут иметь разные базовые классы, что, вероятно, не то, что вам нужно. Вы можете решить это, сохранив не шаблон Base и перемещая метод получения и установки в шаблон промежуточного класса, который является производным от Base а затем производные типы происходят из этого.

class Base { ... };

template<typename T = void> class GetterSetterImpl : public Base { ... };

class Derived : public GetterSetterImpl<Derived> { ... };
1

Другие решения

Компилятор прав: хотя перечисления Base::MyEnum а также Derived::MyEnum определяются в классах, связанных через наследование, сами перечисления не считаются связанными. Они просто имеют одинаковые неквалифицированный имя, которое ничего не значит для компилятора: что касается компилятора, два типа enum не связаны.

Если вы думаете о том, как перечисления реализованы внутри, это имеет смысл: несмотря на строгую типизацию, перечисления остаются небольшими интегральными константами. Поскольку эти два не связаны, Base::MyEnum::A будет иметь то же значение, что и Derived::MyEnum::Cи во время выполнения не будет ничего, что позволяло бы вам различать эти два значения.

Помимо сброса всех значений перечисления в enum базового класса (который убивает возможности расширяться за пределы вашей собственной библиотеки) мало что можно сделать: Перечисления C ++ не поддерживают наследование, и, как правило, не очень гибкие.

1

Это плохая идея, и она не сработает.

  • Уровень языка Вы не можете переопределить вещи в C ++. Вы можете скрыть (не полностью) вещи за новыми, не связанными вещами с тем же именем. Вы также можете переопределить виртуальную функцию, предоставив ей новую реализацию, сохранив при этом свою подпись.
  • Уровень дизайна Ваш базовый класс определяет контракт, которому должны следовать производные классы. Если Base::get() возвращает MyEnum {A, B}тогда каждый производный класс должен иметь get() который возвращает MyEnum {A, B}, Вот в чем заключается наследование (а не повторное использование кода или не просто повторное использование кода в любом случае).

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

1