Исключения позволяют действительно легко написать код, в котором генерируемое исключение нарушит инварианты и оставит объекты в несогласованном состоянии. По сути, они заставляют вас помнить, что большинство ваших высказываний потенциально могут генерировать, и правильно с этим справляться. Это может быть сложно и нелогично.
Рассмотрим что-то вроде этого в качестве простого примера:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Предполагая, что FrobManager
будет delete
FrobObject
, это выглядит хорошо, верно? Или, может быть, нет ... Представьте себе, если либо FrobManager::HandleFrob()
, либо operator new
выдает исключение. В этом примере приращение m_NumberOfFrobs
не откатывается. Таким образом, любой, кто использует этот экземпляр Frobber
, будет иметь возможно поврежденный объект.
Этот пример может показаться глупым (хорошо, мне пришлось немного растянуть себя, чтобы создать один :-)), но вывод заключается в том, что если программист не постоянно думает об исключениях и следит за тем, чтобы каждая перестановка состояние откатывается всякий раз, когда есть броски, таким образом вы попадаете в неприятности.
Например, вы можете думать об этом так же, как о мьютексах. Внутри критического раздела вы полагаетесь на несколько операторов, чтобы убедиться, что структуры данных не повреждены и что другие потоки не могут видеть ваши промежуточные значения. Если какое-либо из этих утверждений просто случайно не выполняется, вы попадаете в мир боли. Теперь уберите блокировки и параллелизм, и подумайте о каждом из этих методов. Думайте о каждом методе как о транзакции перестановок в состоянии объекта, если хотите. В начале вашего вызова метода объект должен быть в чистом состоянии, а в конце также должно быть чистое состояние. Между ними переменная foo
может не соответствовать bar
, но ваш код в конечном итоге исправит это. Что означает исключение, так это то, что любое из ваших утверждений может прервать вас в любое время. В каждом отдельном методе лежит ответственность за вас, чтобы все было правильно и откатывался, когда это происходит, или упорядочивал ваши операции так, чтобы броски не влияли на состояние объекта. Если вы ошиблись (а такую ошибку легко совершить), то вызывающая сторона в итоге увидит ваши промежуточные значения.
Такие методы, как RAII, которые программисты C ++ любят упоминать в качестве окончательного решения этой проблемы, имеют большое значение для защиты от этого. Но они не серебряная пуля. Это гарантирует, что вы освободите ресурсы при броске, но не освобождает вас от необходимости думать о повреждении состояния объекта и вызывающих, видящих промежуточные значения. Таким образом, для многих людей проще сказать по указанию стиля кодирования: без исключений . Если вы ограничиваете тип кода, который пишете, эти ошибки сложнее представить. Если вы этого не сделаете, то легко ошибиться.
Целые книги были написаны о безопасном кодировании исключений в C ++. Многие эксперты поняли это неправильно. Если это действительно так сложно и имеет так много нюансов, возможно, это хороший признак того, что вам нужно игнорировать эту функцию. : -)