Как бросить хорошие исключения? - PullRequest
13 голосов
/ 17 февраля 2009

Я слышал, что вы никогда не должны бросать строку, потому что не хватает информации, и вы будете ловить исключения, которые вы не ожидаете поймать. Какова хорошая практика для создания исключений? Вы наследуете базовый класс исключений? У вас много исключений или мало? вы делаете MyExceptionClass & или const MyExceptionClass &? и т. д. Также я знаю, что исключения не должны создаваться в деструкторах

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

Ответы [ 12 ]

12 голосов
/ 17 февраля 2009

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

Учитывая эти две функции-члена:

const Apple* FindApple(const wchar_t* name) const;
const Apple& GetApple(const wchar_t* name) const;

Имена этих функций, а также их возвращаемые значения указывают мне, что в случае FindApple функция вполне способна возвращать NULL, когда правильное яблоко не было найдено, но в случае GetApple вы ожидаете возвращения яблока. Если эта вторая функция не может сдержать свое обещание, она должна выдать исключение.

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

Обратите внимание, что в случае FindApple звонящий сам должен решить, как обработать условие "не найти правильное яблоко", поскольку оно больше не является исключительным условием.

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

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

Dave

9 голосов
/ 17 февраля 2009

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

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

4 голосов
/ 17 февраля 2009

Используйте стандартные исключения! Если у вас есть конкретная ошибка, попробуйте избежать ее с помощью возвращаемого значения. Если у вас есть для использования исключений, определите свое пользовательское исключение, которое наследуется от Exception, и создайте пользовательское сообщение.

3 голосов
/ 17 февраля 2009

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

В этой ситуации пользовательский класс - лучшее решение. Я использую этот подход, определяя свои собственные встроенные классы (для них нет .cpp; только .h), например ::100100

class DeviceException {
    ;
}

class DeviceIOException: public DeviceException {
    DeviceIOException(std::string msg, int errorCode);
}

и т.д.

Затем я могу судить / действовать в отношении исключения по типу и информации, содержащейся в нем.

1 голос
/ 18 февраля 2009

Вы всегда должны выдавать класс исключения, производный от std :: exception. Это обеспечивает определенную согласованность вашего интерфейса и обеспечивает большую гибкость для клиентов этих методов или функций. Например, если вы хотите добавить обработчик catch all, вы можете добавить блок

catch(std::exception& e)
и покончить с этим. (Хотя часто вы не сможете сойти с рук, если не будете контролировать весь код, который может генерировать).

Я склонен выдавать только исключения, предусмотренные стандартом (например, std :: runtime_error), но если вы хотите обеспечить дополнительную детализацию для ваших обработчиков, вы можете свободно выводить свои собственные из std :: exception. См. C ++ FAQ Lite .

Кроме того, вы должны выбросить временный файл и перехватить его по ссылке (чтобы избежать копирования ctor на вашем сайте перехвата). Бросание указателей также не одобряется, так как неясно, кто должен очистить память. C ++ FAQ Lite тоже с этим справляется.

1 голос
/ 17 февраля 2009

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

При использовании класса или иерархии классов следует учитывать несколько моментов:

  1. И конструктор копирования, и деструктор объекта исключения никогда не должен бросать исключение. Если они твоя программа будет немедленно прекратить. (ISO 15.5 / 1)

  2. Если у ваших объектов исключений есть база классы, затем использовать публичное наследование.
    Обработчик будет выбран только для выведено в базовый класс, если база класс доступный . (ISO 15.3 / 3)

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

Например:

class Ex {
public:
  Ex(int i) 
  : m_i (i)
  {
    if (i > 10) {
      throw "Exception value out of range";
    }
  }

  int m_i;
};


void foo (bool b) {
  if (! b) {
     // 'b' is false this is bad - throw an exception
     throw Ex(20);    // Ooops - throw's a string, not an Ex
  }
}
1 голос
/ 17 февраля 2009

Я всегда выкидываю исключение с сообщением о том, где это произошло и что вызвало это:

throw NException("Foo::Bar", "Mungulator cause a stack overflow!");

Затем вы можете использовать эти строки в ящиках сообщений и т. Д.

Я всегда ловлю через

catch (NException& ex) { ... }

Если вы работаете в Windows, вы можете передать значение ошибки и получить функцию получения сообщения об ошибке. Лучший пример этого - Windows через C / C ++ Джеффри Рихтера .

0 голосов
/ 04 февраля 2013

Из C ++ FAQ, [17.12] Что мне выбросить? :

Как правило, лучше бросать объекты, а не встроенные модули. Если возможно, вы должны бросить экземпляры классов, которые происходят (в конечном счете) из класса std::exception.

... и

Самая распространенная практика - бросать временное: (см. Следующий пример)

0 голосов
/ 04 июля 2009

Вот простой пример генерирования исключения, которое потребует практически никаких ресурсов:

class DivisionError {};
class Division
{
public:
    float Divide(float x, float y) throw(DivisionError)
    {
        float result = 0;
        if(y != 0)
            result = x/y;
        else
            throw DivisionError();
        return result;
    }
};

int main()
{
    Division d;
    try
    {
        d.Divide(10,0);
    }
    catch(DivisionError)
    {
        /*...error handling...*/
    }
}

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

0 голосов
/ 17 февраля 2009

Если вы генерируете исключения из компонента, который другие разработчики будут использовать в нисходящем потоке, пожалуйста, сделайте им большую пользу и всегда извлекайте ваши собственные классы исключений (если они вам действительно нужны c.f с использованием стандартных исключений) из std :: exception. Любой ценой избегайте таких мерзостей, как метание целых, HRESULTS, char *, std :: string ...

...