Как бросить хорошие исключения?

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

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

Ответ 1

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

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

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

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

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

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

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

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

Dave

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

class DeviceException {
    ;
}

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

и др.

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

Ответ 5

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

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

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

Я всегда поймаю через

catch (NException& ex) { ... }

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

Ответ 6

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

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

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

  • Если ваши объекты исключения имеют базу классов, а затем использовать публичное наследование.
    Обработчик будет выбран только для выводится на базовый класс, если базовая класса (ISO 15.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 a string, not an Ex
  }
}

Ответ 7

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

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

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

Кроме того, вы должны бросить временное и поймать его по ссылке (чтобы избежать копирования ctor на ваш сайт catch). Точки броска также не одобряются, поскольку неясно, кто должен очищать память. С++ FAQ Lite также относится к этому.

Ответ 8

Для текущего проекта мы подумали о соответствующих действиях, которые могут быть предприняты основным циклом программы. Базовая программа принимает XML-сообщения и сохраняет информацию в базе данных (с достаточным количеством обработки между ними). ​​

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

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

Ответ 9

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

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

public void DoSomethingWithFile() {
    if(!File.Exists(..))
        throw new FileNotFoundException();
}

Предоставить другой метод для вызова пользователя:

public bool CanDoSomething() {
    return File.Exists(..);
}

Таким образом, вызывающий может избежать исключений, если захочет. Не стесняйтесь бросать, если что-то не так - "не быстро", но всегда предоставляйте исключение.

Также сохраните иерархию классов исключений и взгляните на стандартные исключения, такие как InvalidStateException и ArgumentNullExcpetion.

Ответ 10

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

Ответ 11

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

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...*/
    }
}

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

Ответ 12

Из часто задаваемых вопросов по С++ [17.12] Что мне делать?:

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

... и

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