2015-08-04 3 views
5

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

Например, в конструкторе:

#include <stdexcept> // std::invalid_argument 
#include <string> 

class Foo 
{ 
public: 
    void Foo(int hour, int minute) 
    :h(hour), m(minute) 
    { 
     if(hour < 0 || hour > 23) 
      throw std::invalid_argument(std::string("...")); 
     if(minute < 0 || minute > 59) 
      throw std::invalid_argument(std::string("...")); 
    } 
} 

Примечание: Это пример, пожалуйста, не отвечайте с ограниченными целыми числами.

Вызов пользователей с помощью foo(23, 62);, как обработчик исключений пользователя может различать два возможных экземпляра std::invalid_argument?

Или я делаю это неправильно, и я должен унаследовать от std :: invalid_argument, чтобы различать их? То есть,

class InvalidHour: public std::invalid_argument 
{ 
public: 
    InvalidHour(const std::string& what_arg) 
    :std::invalid_argument(msg) {}; 
} 

class InvalidMinute: public std::invalid_argument 
{ 
public: 
    InvalidMinute(const std::string& what_arg) 
    :std::invalid_argument(msg) {}; 
} 

Затем бросить InvalidHour и InvalidMinute?

Редактировать: Создание класса для каждого возможного исключения кажется мне слишком большим, особенно в большой программе. Всякая программа, которая эффективно использует исключения, таким образом, содержит обширную документацию о том, что нужно поймать?

Как уже упоминалось в ответе, я рассмотрел assert. Оглядываясь на stackoverflow, я обнаружил, что большинство людей говорят, что вы должны выбросить исключение (как мой частный случай для конструктора ).

Оглядываясь на множество онлайн-информации о том, когда использовать исключения, общий консенсус заключается в использовании assert для логических ошибок и исключений для ошибок времени выполнения. Хотя, вызывая foo(int, int) с недопустимыми аргументами может быть ошибкой во время выполнения. Это то, к чему я хочу обратиться.

+4

* В идеале * вы должны иметь два типа: 'Hour' и' Minute', которые генерируют свой собственный тип исключения при построении с недопустимыми значениями. – user657267

+0

Да, вы правы, вам следует вводить новые типы, если вы хотите отличить их в опрятной моде –

+3

* «как пользователь сможет отличить исключения, которые могут выполнять мои функции» * ** Почему ** вы нужен пользователь/нужно ли пользователю различать эти два случая? Проверяет ли 'foo' свои аргументы? – dyp

ответ

4

Стандартная иерархия исключений непригодна для логических ошибок. Используйте assert и сделайте с ним. Если вы абсолютно хотите преобразовать жесткие ошибки в трудные для обнаружения ошибок времени выполнения, тогда обратите внимание, что только обработчик может выполнять только две разумные вещи: достичь договорной цели каким-то другим способом (возможно, просто повторить операцию) или в превратите бросок в исключение (обычно просто перетаскивая), и что точная причина оригинального исключения редко играет какую-либо роль в этом. Наконец, если вы хотите поддерживать код, который действительно пытается использовать различные комбинации аргументов до тех пор, пока не найдет тот, который не выбрасывает, как бы глупо это выглядело теперь, когда оно выписано словами, ну, у вас есть std::system_error для передачи целочисленной ошибки код вверх, но вы можете определить производные классы исключений.

Все, что сказано, идите за assert.

Это то, для чего оно предназначено.

+0

Что делать, если это ошибка времени выполнения? Прекращение в этом случае не будет оправдано, не так ли? –

+0

@ShreyasVinod: Термин «ошибка времени выполнения» обычно относится к обработке некоторых ошибок во время выполнения, но нет никакого значения недопустимого аргумента. Таким образом, у меня есть только смутная возможность того, что вы имеете в виду, но я думаю, что все, что угодно, может помочь думать о функции как о ** контракте **. Он имеет некоторые требования, например. по значениям аргументов, и если эти требования выполнены, то он обязан произвести определенный результат или, если он не удается, выбросить исключение.В противном случае при расторжении договора гарантия аннулируется. Это похоже на двигатель автомобиля: дайте ему газ, хорошо, дайте ему сахарную воду! –

+0

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

1

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

1

Стандартные исключения не позволяют хранить дополнительную информацию, которую вы хотите, а синтаксический анализ сообщений об исключениях - плохая идея.Одно из решений заключается в подклассе, как вы упомянули. Есть и другие - с появлением std::exception_ptr. можно использовать «внутренние» (или «вложенные») исключения, как в Java или .NET, хотя эта функция более применима к трансляции исключений. Некоторые предпочитают Boost.Exception, что является еще одним решением для исключений, расширяемых во время выполнения.

Не попадайте в «справедливую ловушку», как Cheers и hth. Простой пример:

void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen) 
{ 
    assert(fromLen <= bufLen); 
    std::copy(from, from + fromLen, buf); 
} 

Там нет ничего плохого с assert сами по себе, но если код компилируется для выпуска (с NDEBUG набора), то safe_copy не будет в безопасности на всех, и результат может быть переполнение буфера , что потенциально позволяет злоумышленнику принять этот процесс. Выброс исключения для указания логической ошибки имеет свои проблемы, как уже упоминалось, но, по крайней мере, это предотвратит немедленное неопределенное поведение в сборке релизов. Поэтому я хотел бы предложить, в функциях безопасности критически важная, использовать утверждение в отладочном и исключение в релизе сборке:

void safe_copy(const char *from, std::size_t fromLen, char *buf, std::size_t bufLen) 
{ 
    assert(fromLen <= bufLen); 
    if (fromLen > bufLen) 
     throw std::invalid_argument("safe_copy: fromLen greater than bufLen"); 
    std::copy(from, from + fromLen, buf); 
} 

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

+0

Я как-то (как и 15 лет назад) завернул решение, которое вы представляете, комбо с утверждением и броском исключения, и опубликовал статью об этом в Интернете (сегодня это будет публикация блога). Чтобы быть немного менее опасным, жесткое исключение, распространяющееся вверху, исключение, заменяющее «assert», не должно быть стандартным исключением. Оказалось, что я не использовал эту вещь позже. И никто другой не сделал этого. Это похоже на идею определения макроса 'WITH' (да BTDT тоже). Поэтому я думаю, что ваше увольнение из моего, по-видимому, простого совета немного преждевременно. Это основано на том, что вы изобретаете свое решение. –

+1

Хм, в то время как я нахожусь в этом, мне лучше заметить, что проверка достоверности аргументов, которая не проверяет огромные значения беззнаковых целочисленных аргументов, хуже, чем проверка. Дает ложное ощущение безопасности, не проверяя наиболее распространенную ошибку (обход отрицательного значения). Если вы действительно хотите, возможно, большой 64-битный диапазон для 64-битных сборников, и вы хотите описательное имя, то 'using Size = ptrdiff_t' является вашим другом. –

1

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

Например, посмотрите на этот код:

struct Node 
{ 
    int data; 
    Node* next; 
    Node(int d) : data(d), next(nullptr) {} 
}; 

// some code 
Node* n = new Node(5); 
assert(n && "Nodes can't be null"); 

// use n 

Когда этот код построен в режиме RELEASE, что утверждение «не существует», и вызывающий абонент может получить n быть nullptr во время выполнения.

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