Подтвердить что ты не робот

В минусе исключения в С++

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

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

В частности, утверждение, которое я не понял, следующее:

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

и это:

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

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

4b9b3361

Ответ 1

"записывает в постоянное состояние" означает примерно "запись в файл" или "запись в базу данных".

"в фазу 'commit'. означает грубо" Выполнение всей записи сразу "

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

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

Ответ 2

В основном, современный С++ использует управление ресурсами с ограничением видимости (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.)

Ответ 3

Что это говорит о постоянном состоянии, это: даже если вы используете 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, когда генерируется исключение. Они просто говорят, что принудительная семантика транзакций может загромождать (обфускать) код.

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

Ответ 4

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

Я удивляюсь, что больше людей не играли в эту линию. Это обсуждается "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, но это всего лишь пара строк.

Это далеко не полный контрольный показатель обработки исключений. См. ISO/IEC TR18015 Технический отчет по производительности С++, раздел 5.4, для более авторитетного и тщательного ответа. Имейте в виду, что они начинаются с примера с почти тривиальным:

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 здесь и здесь (где я снял вышеуказанную ссылку, любезно предоставил Ксавье Нодет).

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

Ответ 5

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

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

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