В чем разница между броском и броском с аргументом исключенного?
Представьте себе два похожих фрагмента кода:
try {
[...]
} catch (myErr &err) {
err.append("More info added to error...");
throw err;
}
и
try {
[...]
} catch (myErr &err) {
err.append("More info added to error...");
throw;
}
Являются ли они такими же или отличаются ли они каким-то тонким способом? Например, первый из них вызывает запуск конструктора копий, тогда как, возможно, второй повторяет один и тот же объект для его ретронирования?
Ответы
Ответ 1
В зависимости от того, как вы упорядочили свою иерархию исключений, повторное бросание исключения путем присвоения имени переменной исключения в инструкции throw может срезать исходный объект исключения.
Выражение выражения без аргумента будет вызывать текущий объект исключения, сохраняющий его динамический тип, тогда как выражение throw с аргументом генерирует новое исключение, основанное на статическом типе аргумента, на throw
.
например.
int main()
{
try
{
try
{
throw Derived();
}
catch (Base& b)
{
std::cout << "Caught a reference to base\n";
b.print(std::cout);
throw b;
}
}
catch (Base& b)
{
std::cout << "Caught a reference to base\n";
b.print(std::cout);
}
return 0;
}
Как указано выше, программа выведет:
Caught a reference to base
Derived
Caught a reference to base
Base
Если throw b
заменить на throw
, то внешний catch также поймает изначально выброшенное исключение Derived
. Это все еще выполняется, если внутренний класс ловит исключение Base
по значению вместо ссылки - хотя это, естественно, означает, что исходный объект исключения не может быть изменен, поэтому любые изменения в b
не будут отображаться в Derived
исключение, пойманное внешним блоком.
Ответ 2
Во втором случае в соответствии с конструктором копирования С++ Standard 15.1/6 не используется:
Выражение-выражение без операнда перепроводит обрабатываемое исключение. Исключение возобновляется с существующим временным; не создается новый объект временного исключения. Исключение больше не считается пойманным; поэтому значение uncaught_exception() снова будет истинным.
В первом случае новое исключение будет выбрано в соответствии с 15.1/3:
Выражение-выражение инициализирует временный объект, называемый объектом исключения, тип которого определяется удалением всех cv-квалификаторов верхнего уровня из статического типа операнда броска и настройки тип из "массива T" или "функция, возвращающая T", на "указатель на T" или "указатель на функцию возврата T", соответственно. <... > Временное значение используется для инициализации переменной, указанной в соответствующем обработчике (15.3). Тип выражения-броска не должен быть неполный тип или указатель или ссылка на неполный тип, кроме void *, const void *, volatile void * или const volatile void *. За исключением этих ограничений и ограничений на типа, упомянутого в 15.3, операнд throw обрабатывается точно как аргумент функции в вызове (5.2.2) или операндом оператора return.
В обоих случаях конструктор копирования требуется на этапе броска (15.1/5):
Когда брошенный объект является объектом класса, а конструктор копирования, используемый для инициализации временной копии, недоступен, программа плохо сформирована (даже если временный объект в противном случае можно было бы исключить). Аналогично, если деструктор для этого объекта недоступен, программа плохо сформирована (даже если временный объект может быть удален).