Зачем бросать класс поверх перечисления? - PullRequest
5 голосов
/ 19 февраля 2011

Просто интересно, почему лучше бросить класс над перечислением

Конечно, бросать классы - это больше накладных расходов?

, например

enum MyException
{
   except_a,
   except_b,
   except_c
}


void function f(){
  throw a;
}


int main(int arc, char* argv[]){

  try{

  } catch (MyException e){
    switch(e){
      except_a: break;
      except_b: break;
      except_c: break;
    }
  }

  return 0;
}

Помимо накладных расходов. Мне также нужно объявить класс для каждого, который может переопределить std :: exception или что-то. Больше кода, больше двоичного кода ... в чем выгода?

Ответы [ 6 ]

9 голосов
/ 19 февраля 2011

Какой из следующих двух catch блоков легче понять:

try {
    do_something();
}
catch (const int&) {
    // WTF did I catch?
}
catch (const std::out_of_range&) {
    // Oh, something was out of range!
}

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


Чтобы рассмотреть ваш обновленный пример использования перечисления вместо целого числа, что из следующего яснее:

try{
    do_something();
} 
// (1) Catch an enum:
catch (MyException e) {
    switch(e) {
    except_a: break;
    except_b: break;
    except_c: break;
    default:  throw; // Don't forget, you have to throw any exceptions 
                     // that you caught but don't actually want to catch!
    }
}
// (2) Catch specific exceptions
catch (const ExceptionA&) { }
catch (const ExceptionB&) { }
catch (const ExceptionC&) { }

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


Что касается вашего "накладного" вопроса, это зависит, но, вероятно, нет. Должно быть (относительно) редко, чтобы генерировалось исключение, и если у вас узкий цикл, где производительность действительно имеет значение, вы все равно не будете использовать исключения.

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

4 голосов
/ 19 февраля 2011

Дано

enum MyException
{
   except_a,
   except_b,
   except_c
}

написать предложение catch, которое перехватывает только except_c исключений.

С

struct my_except {};
struct my_except_a : my_except {};
struct my_except_b : my_except {};
struct my_except_c : my_except {};

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

Многие из общих преимуществ деривации применяются к исключениям. Например, базовые классы могут заменять производные классы, а код должен знать только базовые классы, чтобы иметь дело с производными исключениями. Это форма полиморфизма, тогда как enum - это переключение типа.

Здесь также действует общее правило о полиморфизме: Всякий раз, когда у вас возникает соблазн использовать переключатель над типом, вы отвергаете преимущества полиморфизма. Проблемы, с которыми вы сталкиваетесь Вы увидите, как только код расширится до сотен kLoC, и вам нужно будет добавить новый тип. С полиморфизмом это легко, потому что большая часть кода будет работать только с ссылками на базовый класс.
С типом enum вы должны искать каждый switch оператор над этим enum и проверять, нужно ли его адаптировать.

Подобные вещи убили не одну компанию.


Вот запоздалая мысль: когда они делают это некоторое время, пользователи обычно начинают добавлять все виды данных к своим типам исключений. Классика - это взять __FILE__ и __LINE__ в конструкторе исключения, чтобы увидеть, откуда возникло исключение. Но это тоже требует исключений, чтобы быть типами классов.

3 голосов
/ 19 февраля 2011

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

1 голос
/ 19 февраля 2011

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

Кроме того, значение enum в качестве единственного члена класса можно копировать так же быстро, как и значение enum без класса вокруг него.Исключения могут быть как блокноты для случайных заметок, чтобы выйти из замятия.Маловероятно, что вам понадобится больше данных, которые сначала (удушье) удвоят накладные расходы по сравнению с двумя int s.

Самый простой и лучший способ - запуститьс std:exception, и только НИКОГДА классы броска, полученные из него.

0 голосов
/ 19 февраля 2011

Накладные расходы не имеют значения, так как исключения всегда дороги. Экземпляр класса может содержать больше информации, и вы можете ловить по базам.

0 голосов
/ 19 февраля 2011

Речь идет не о производительности. Выдача исключений - не та вещь, которую вы хотите регулярно выполнять в алгоритме с интенсивным использованием процессора. А накладные расходы на разматывание стека намного больше, чем перенос брошенного объекта.

Речь идет о возможности связать информацию о том, какая ошибка произошла. Речь идет о том, чтобы разъяснить пользователю вашей библиотеки, что вы выдаете за ошибку и почему. Целое число или число с плавающей запятой на самом деле не несет много информации.

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