Расширение enum в производных классах - PullRequest
12 голосов
/ 07 сентября 2010

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

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 мог содержать любое из трех значений.

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

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

Ответы [ 8 ]

11 голосов
/ 07 сентября 2010

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

Более того, поскольку перечисления C ++ не являются классами, их нельзя расширять или наследовать. Вы можете определить дополнительные причины в отдельном перечислении в 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 ???)
    ...
}

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

9 голосов
/ 07 сентября 2010

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

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

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 };
};
2 голосов
/ 07 сентября 2010

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

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

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

1 голос
/ 07 сентября 2010

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

#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';
}
1 голос
/ 07 сентября 2010

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

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

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

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

1 голос
/ 07 сентября 2010

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

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

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

1 голос
/ 07 сентября 2010

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

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

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

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

0 голосов
/ 07 сентября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...