Путаница с функциями без бросков

У меня есть 2 вопроса о функциях без метания:

  1. Зачем делать функцию без метания?

  2. Как сделать функцию без метания? Если код внутри функции на самом деле может throw, тогда я должен все еще сделать это не бросающим?

Вот пример:

void swap(Type t1, Type t2) throw()
{
    //swap
}

Если код в swap не будет бросать вообще, я должен еще добавить throw()? Зачем?

2 ответа

Решение

throw() (или же noexcept в C++11) полезно по двум причинам:

  1. Это позволяет компилятору быть более агрессивным в своих оптимизациях.
  2. Он сообщает пользователям функций, что они могут использовать эту функцию в своих собственных функциях, не являющихся бросающими.

Не бросающие функции очень важны для написания безопасного кода исключений. Например, обычный способ написания исключений безопасно operator= через не бросая swap() функция.

С другой стороны, другие спецификации исключений бесполезны и были справедливо признаны устаревшими в текущем стандарте. Они плохо сочетаются с шаблонами и слишком дороги для применения.

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

void f() noexcept
{
  a();
  b();
}

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

РЕДАКТИРОВАТЬ: Теперь для второй части вашего вопроса: как сделать функцию без броска?

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

class C
{
  C* CreateInstance()
  {
    return new C();
  }
}

С оператором new может бросить std::bad_alloc, CreateInstance() могу бросить. Вы можете попытаться избежать этого с помощью блока try-catch, обрабатывая или проглатывая любые исключения, которые могут быть выброшены внутри try блок, но это действительно разумно? Например:

C* CreateInstance()
{
  try
  {
     return new C();
  }
  catch (...)
  {
     return null;
  }
}

Кажется, проблема решена, но готовы ли ваши абоненты к CreateInstance() возврате null? Если нет, произойдет сбой, когда они попытаются использовать этот указатель. Кроме того, std::bad_alloc обычно означает, что вам не хватает памяти, и вы просто отложите проблему.

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

Зачем делать функцию без метания?

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

Как сделать функцию без метания? Если код внутри функции на самом деле может выдать, то я должен все еще сделать его не бросающим?

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

void swap(T t1, T t2) noexcept {
  try {
    // ....
  } catch (const std::exception& ex) {
    // ....
  } catch (...) {
    // ....
  }

}

Если вы утверждаете, что ваша функция не генерирует, вы должны сделать так, чтобы она не генерировала. Но это не означает добавление спецификации исключений, чего не следует делать, поскольку она устарела. Это означает, что вы должны убедиться, что функция не работает, несмотря ни на что. Ключевое слово C++11 noexcept дает вам некоторую безопасность во время компиляции в этом отношении.

Другие вопросы по тегам