2010-07-22 3 views
14

Я читал руководство по стилю Google C++ и запутался в части Exceptions. Один из недостатков его использования, согласно руководству, заключается в следующем:В минусе исключения из C++

Безопасность исключений требует как RAII , так и различных методов кодирования. Лот вспомогательных механизмов необходим для make writing correct exception-safe код легкий. Кроме того, чтобы избежать необходимости считывать читателей, чтобы понять весь график звонка , код, защищенный от ошибок, должен изолировать логику, которая записывает в постоянное состояние в фазу «фиксации» . Это будет иметь как преимущества , так и затраты (возможно, если вы вынуждены , чтобы сфотографировать код, чтобы изолировать фиксацию ). Позволяя исключения будут заставить нас всегда платить эти расходы даже тогда, когда они не стоят

В частности, утверждение, что я не понимаю, это:

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

и это:

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

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

+0

Тип дубликата, см. Http://stackoverflow.com/questions/1849490/ –

+2

Одна из проблем руководства по стилю Google заключается в том, что исключения становятся все более распространенными, а не меньше, поэтому код, который не является безопасным, является хрупким. –

+0

http://gotw.ca/gotw/082.htm – DumbCoder

ответ

8

«пишет постоянное состояние» означает, грубо «пишет в файл» или "записывает в базу данных ».

«в фазу« совершить ».« означает примерно „Делая все письма сразу“

„ может быть, где вы вынуждены запутать код, чтобы изолировать фиксации“ означает примерно» Это может сделать код трудно читать "(Незначительное злоупотребление словом «obfuscate», что означает намеренно сделать что-то трудное для чтения, в то время как здесь они означают, что это затрудняет чтение, но это злоупотребление могло быть преднамеренным, для драматического эффекта).

Разработка более: «пишет к постоянному состоянию "более тесно означает" Выписать, некоторым постоянным СМИ, все подробности об этом объекте, которые понадобятся для r выровняйте его ». Если запись была прервана исключением, тогда эти «выписанные детали» (т. Е. «Постоянное состояние») могут содержать половину нового состояния и половину старого состояния, что приводит к недопустимому объекту при его воссоздании. Следовательно, запись состояния должно выполняться как одно непрерывное действие.

+0

Это похоже на «постоянное состояние», они означают переменные в куче (объединив все это, они могут лучше объяснить исключения). –

+3

На самом деле, «постоянным состоянием» они почти наверняка означают состояние, наблюдаемое аутсайдерами. Это более общий, чем переменные в куче. См., Например, работу над транзакционной памятью программного обеспечения. –

+0

Я согласен с Эриком. Некоторые из ранних проблем с контейнерами STL заключались в том, как иметь дело с частичными обновлениями, когда конструктор генерирует исключение. Это не имеет ничего общего с локальными переменными, но с большей картиной обновляемого объекта. Теперь расширьте это до таких файлов, как файлы и базы данных. – Torlack

37

В основном, современный C++ использует управление ресурсами с ограниченным диапазоном (SBRM или RAII). То есть, объект очищает ресурс в своем деструкторе, который, как гарантируется, будет вызван.

Все это прекрасно и денди, если ваш код не является современным. Например:

int *i = new int(); 
do_something(i); 
delete i; 

Если do_something бросает исключение, вы просочились. Правильное решение не иметь ресурс в дикой природе, как это, т.е .:

std::auto_ptr<int> i(new int()); 
do_something(i.get()); 

Теперь она никогда не может течь (и код чище!).

Я думаю, что руководство пытается сказать, что у нас уже есть весь этот старый код, и использование современного стиля потребует слишком много усилий. Поэтому давайте не будем использовать исключения. (Вместо того, чтобы исправить весь код ... Я не люблю Руководство Стиля Google очень много.)

+20

Раньше я еще не слышал термин «SBRM». Это гораздо более описательно, чем RAII. Надеюсь, он поймает. –

+1

@ Фред: Я тоже, поэтому я использую его везде. Я собираюсь внести изменения в википедию, а также CW Q & A на этом. Я пытаюсь [отследить] (http://stackoverflow.com/questions/3119197/whats-the-right-approach-for-error-handling-in-c/3119202#3119202), где новый термин исходил из , – GManNickG

+8

+1 за неприятие руководства по стилю Google (оно отлично подходит для внутреннего использования, но это не то, что я хотел бы наложить на новый современный проект). NB. Я бы дал вам плюс +1 для остальных :-) –

9

Что это говорит о постоянном состоянии, это: даже если вы используете RAII, и ваш объект будет разрушен должным образом, что позволит вам очистить, если код в блоке try каким-то образом изменил состояние системы, вам, скорее всего, придется выяснить, как отменить эти изменения, потому что операция не завершилась успешно. Они используют термин «фиксация» здесь, когда речь идет о транзакциях, о том, что при выполнении операции состояние системы должно быть таким, как если бы оно было завершено на 100% успешно или вообще не произошло.

Вот как это может получить перепутано даже с RAII:

struct MyClass 
{ 
    MyClass(Foo* foo) 
    { 
     m_bar = new Bar; 
     foo->changeSomeState(); 
    } 

    ~MyClass() 
    { 
     delete m_bar; 
    } 

    Bar* m_bar; 
}; 

Теперь, если у вас есть этот код:

try 
{ 
    MyClass myClass(foo); 
    Baz baz; 
    baz.doSomething(); // Throws an exception 
} 
catch(...) 
{ 
    // MyClass doesn't leak memory, but should it try to undo 
    // the change it made to foo? 
} 

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

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

+0

Это не дает никаких оснований делать все это одновременно. Для этой ситуации вам нужны небольшие блоки try/catch. Единственное, что делает исключение, это заставить вас исправить проблему, без них у вас все еще будет проблема, вы просто не знаете об этом. –

+0

@Brendan - Исключения действительно не помогают с проблемой, о которой вы говорите. Это просто дает вам другой метод (и более чистый IMHO) для решения этой проблемы. Ленивые люди все равно будут лениться. – Torlack

+2

@Brendan Long Очевидно, что можно сделать правильную обработку ошибок без исключений. Я предпочитаю их, но, как я уже сказал, я не согласен с их рассуждениями здесь. Но я думаю, что это определенно иллюстрирует аргумент, который они делают, и это было вопросом вопроса. – bshields

2

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

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

void SomeMethod(){ 
    m_i++; 
    SomeOtherMethod(); 
    m_j++; 
} 
+3

Первая статья Joel on Software - с 2003 года, вскоре после того, как люди выяснили, что такое безопасное исключение. Второй имеет мало общего с исключениями, за исключением указания на статью Чэнь. Статья Раймонда Чена наглядно демонстрирует, что Чен не знал, о чем он говорил в то время (один надеется и ожидает, что теперь он знает больше об исключительной безопасности). И, да, этот код, скорее всего, ошибается, но, не зная, что он должен делать, и что представляют собой инварианты класса, невозможно сказать. Очевидно, что m_i может увеличиваться, а m_j - нет. –

+5

Я продолжу нашу дискуссию из моего ответа здесь. В обоих пунктах 1 и 2 в первой статье я говорю: «И что?». Мне не нужно знать, может ли быть вызвано исключение, потому что я понимаю, что если что-то плохое. Если я в состоянии справиться с этим, я буду, иначе кого это волнует? Пусть кто-то выше в цепочке решает проблему. У меня здесь нет потерь. Вторая статья - чистая фиктивная дерьмо: «У меня есть dosomething() и cleanup(), если dosomething throws я не буду очищать!» Ну, черт возьми, вот почему вы кладете вещи в контейнеры, которые убирают * для вас *. Черт, теперь ваш код еще чище ... – GManNickG

+2

... потому что вам даже не нужно быть явным о том, чтобы очистить. Я не вижу смысла в третьей статье. Насколько я могу судить, это просто не признает цели исключений. (Обращайтесь с ним, если можете, и если нет, то он автоматически перейдет к кому-то, кто может.) Гораздо лучше, чем коды ошибок (лучше не забывать обращаться с ним, или * никто никогда не справится с этим.) Что касается вашего фрагмента кода в ваш ответ, боюсь, я не вижу смысла. Что именно не так? Если 'SomeOtherMethod' выдает исключение, * почему я хочу продолжить? * – GManNickG

3

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

Я удивлен тем, что в эту линию не попало больше людей. Это обсуждается «con»: обработка исключений является дорогостоящей. Остальная часть параграфа - это только детали того, почему требуется столько машин.

Это недостаток исключений, которые обычно игнорируются на двухъядерных 2 ГГц машинах с 4 ГБ ОЗУ, 1 ТБ жестким диском и кусками виртуальной памяти для каждого процесса. Если код легче понять, отладить и написать, затем купить/сделать быстрее аппаратное обеспечение и написать узкие места без исключений, а также в C.

Однако в системе с более жесткими ограничениями вы не можете игнорировать накладные расходы. Попробуй это. Сделайте файл test.cpp следующим образом:

//#define USE_EXCEPTIONS 
int main() { 
    int value; 
#ifdef USE_EXCEPTIONS 
    try { 
#endif 
    value++; 
#ifdef USE_EXCEPTIONS 
    if (value != 1) { 
     throw -1; 
    } 
    } 
    catch (int i) { 
     return i; 
    } 
#else 
     return -1; 
} 
#endif 

    return value; 
} 

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

Скомпилируйте его в любом случае с
g++ -S -nostdlib test.cpp
и посмотрите на полученный файл сборки test.s. Шахта имела длину 29 строк без блока if (value != 1) { return -1 }, или 37 строк с примером тестового блока возврата. Большая часть из них была ярлыками для компоновщика.

После того как вы удовлетворены этим кодом, раскомментируйте опцию #define USE_EXCEPTIONS в верхней части страницы и скомпилируйте ее снова. Wham! 155 строк кода для обработки исключения. Я дам вам, что теперь у нас есть дополнительный оператор return и конструкция if, но это всего лишь пара строк.

Это далеко не полный контрольный показатель обработки исключений. Для более авторитетного и тщательного ответа см. Раздел 5.4 ISO/IEC TR18015 Technical Report on C++ Performance. Обратите внимание, что они начинают с почти-как-тривиальным примером:

double f1(int a) { return 1.0/a; } 
double f2(int a) { return 2.0/a; } 
double f3(int a) { return 3.0/a; } 
double g(int x, int y, int z) { 
    return f1(x) + f2(y) + f3(z); 
} 

так что есть смысл в использовании абсурдно небольшие тестовые случаев. Существуют также потоки StackOverflow here и here (где я снял вышеуказанную ссылку, любезно предоставил Ксавье Нодет).

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

+2

Вы не обрабатываете одни и те же «ошибки» в не исключительном случае, поэтому это яблоки и апельсины. – ergosys

+2

Этот код представляет собой сравнение обработки ошибок с тем, что он ничего не делает, а не исключения против ... ну, что бы вы ни пытались сравнить. – GManNickG

+0

Точка взята, поэтому я добавил 'if (значение!= 1) {return -1} '. Здесь больше ничего не требуется. Это добавило менее 10 строк в сборку. Дело в том, что система исключений должна быть разработана для обработки всех видов прецедентов, таких как безопасность потоков, автоматическое уничтожение объектов, уничтожение частично сконструированных объектов ('throw' в конструкторе), список продолжается - прочитайте Технический отчет , Программа, которая не нуждается в этом, получает 118 строк накладных расходов. –

Смежные вопросы