Подтвердить что ты не робот

Расширение перечисления в производных классах

У меня есть иерархия классов, и каждый класс в ней имеет класс исключения, полученный в параллельной иерархии, таким образом...

class Base
{
};

class Derived : public Base
{
};

class BaseException : public std::exception
{
   enum {THIS_REASON, THAT_REASON};
};

class DerivedException : public BaseException
{
    // er...what?
};

Я хотел бы в классе DerivedException расширить тип перечисления, чтобы включить новое значение THE_OTHER_REASON, так что класс DerivedException мог содержать любое из трех значений.

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

РЕДАКТИРОВАТЬ: Возможный дубликат был предложен здесь, но предлагаемые решения разные, потому что этот вопрос задан для С# и для С++.

4b9b3361

Ответ 1

С точки зрения ОО, это не разумно. Поскольку вы говорите, что DerivedException is-a BaseException, его возможные причины должны быть подмножеством от BaseException, а не надмножеством. В противном случае вы в конечном счете нарушите Принцип замены Лискова.

Кроме того, поскольку перечисления С++ не являются классами, вы не можете их расширять или наследовать. Вы можете определить дополнительные причины в отдельном перечислении в DerivedException, но затем в конечном итоге вы столкнетесь с одной и той же проблемой, описанной выше:

class DerivedException : public BaseException
{
  enum {
    SOME_OTHER_REASON = THAT_REASON + 256, // allow extensions in the base enum
    AND_ANOTHER_REASON
  };
  ...
};

...
try {
  ...
} catch (BaseException& ex) {
  if (ex.getReason() == BaseException::THIS_REASON)
    ...
  else if (ex.getReason() == BaseException::THAT_REASON)
    ...
  else if (ex.getReason() == ??? what to test for here ???)
    ...
}

Вместо этого вы можете определить отдельный подкласс исключения для каждой отдельной причины. Затем вы можете обрабатывать их полиморфно (при необходимости). Это подход стандартной библиотеки С++, а также других библиотек классов. Таким образом, вы придерживаетесь соглашений, что делает ваш код более понятным.

Ответ 2

Для меня разумнее иметь класс исключения для каждой причины исключения. При обработке исключения обычно не интересно знать, какой класс выбрал исключение, но по какой причине было выбрано исключение.

Если вы хотите сохранить свой дизайн: С++ не позволяет расширять существующее перечисление, но вы можете создать новое перечисление, которое начинается там, где было остановлено предыдущее:

class BaseException : public std::exception
{
   enum {THIS_REASON, THAT_REASON, END_OF_BASE_REASONS };
};

class DerivedException : public BaseException
{
   enum {OTHER_REASON = BaseException::END_OF_BASE_REASONS };
};

Ответ 3

Вы можете сделать это, поскольку используете неназванные перечисления. Я полагаю, вы используете целочисленный тип для хранения причины исключения.

class DerivedException : public BaseException
{
    enum { YET_ANOTHER_REASON = THAT_REASON + 1, EVEN_MORE_REASON};
};

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

Ответ 4

Во-первых: перечисления не могут быть получены; вам просто не повезло.

Во-вторых, похоже, что вы слишком задумываетесь о своей модели исключения. Вместо того, чтобы каждый класс использовал настраиваемое производное исключение, специфичное для этого класса, создайте серию исключений, которые имеют семантическое значение, которое могут бросить ваши классы. Например, если вы бросаете, потому что требуемый аргумент имеет значение null, вы можете выбросить NullArgumentException. Если вы бросаете из-за переполнения математики, вы можете выбросить ArithmeticOverflowException.

В вашей модели перечисления вы помещаете семантическое значение в перечисление; Я предлагаю вам поместить семантическое значение в тип исключения. Помните, вы можете поймать несколько типов исключений.

Примеры семантически значимых исключений рассмотрите стандартную библиотеку С++ или, для более обширного списка, библиотеки Java или С#.

Ответ 5

Нет, это не разумно, как сейчас. Для того чтобы производный тип имел какое-либо значение (с точки зрения принципа подписи Лискова), в базовом классе должно быть полиморфное поведение.

Вы можете добавить virtual int GetError() const в базовый класс и позволить производным классам переопределить его, но тогда пользователь BaseException* или BaseException& не будет иметь никакого представления о том, что код ошибки, возвращаемый производные классы.

Я бы выделил значения кода ошибки из классов.

Ответ 6

Я хотел бы в классе DerivedException расширить тип перечисления, чтобы включить новое значение THE_OTHER_REASON, так что класс DerivedException мог содержать любое из трех значений.

Просто назначьте первое значение нового перечисления. Это работает, поскольку вы просто используете перечисление как способ объявления констант.

class DerivedException : public BaseException
{
    enum {THE_OTHER_REASON = THAT_REASON + 1, THE_REALLY_OTHER_REASON, ETC};
};

Проблема заключается в том, что вы действительно не можете ожидать уникальности перечислений между производными классами, не определяя "цепочку" производных классов (т.е. один запускает свои перечисления после чужих). Однако, если вам нужна только уникальность в пределах одной ветки дерева классов исключений, она может работать нормально.

Ответ 7

Не существует собственного способа сделать это, но это легко сделать с помощью определений: Вот небольшой пример:

#define _Enum1_values a, b, c, d

enum Enum1 {
    _Enum1_values
};

// there aren't strongly typed enums
class A {
public:
    enum Enum2 {
        _Enum1_values, e, f
    };
};

// there aren't strongly typed enums
class B {
public:
    enum Enum3 {
        _Enum1_values, g, h
    };
};


#include <iostream>

int main() {
    std::cout << "Enum1::d: " << d << '\n';
    std::cout << "Enum2::d: " << A::d << '\n';
    std::cout << "Enum2::e: " << A::e << '\n';
    std::cout << "WARNING!!!:  Enum2::e == Enum3::g: " << (A::e == B::g) << '\n';
}

Ответ 8

"Причина" исключения - это, скорее всего, что-то, что нужно показать пользователю или быть зарегистрированным, поэтому строка кажется более подходящей, поэтому ее можно использовать в what().
Если это более того, требуется больше подклассов.