Стандартные исключения, вызывающие небезопасное использование? - PullRequest
4 голосов
/ 06 сентября 2011

Рекомендуется всегда выдавать что-то, полученное из std::exception, и есть несколько предопределенных специализаций, таких как интерфейс std::runtime_error

std::exception в терминах не-бросающих методов доступа.Отлично.Теперь посмотрим на конструктор для std::runtime_error

class runtime_error : public exception {
public:
  explicit runtime_error (const string &);
};

Так что, если я сделаю этот

try {
    foo ();
}
catch (...) {
    throw std :: runtime_error ("bang");
}

, вполне возможно, что foo бросил, потому что он внепамяти, в этом случае также можно сгенерировать аргумент string для runtime_error.Это было бы бросающим выражением, которое само по себе также выбрасывает: разве это не вызовет std::terminate?

Не значит ли это, что мы всегда должны делать это вместо этого:

namespace {
    const std :: string BANG ("bang");
}

...

try {
    foo ();
}
catch (...) {
    throw std :: runtime_error (BANG);
}

НОПОДОЖДИТЕ, это тоже не сработает, правда?Потому что runtime_error собирается скопировать свой аргумент, который также может выдать ...

... не значит ли это, что не существует безопасного способа использовать стандартные специализации std::exception, ичто вы всегда должны бросать свой собственный строковый класс, конструктор которого не работает без броска?

Или есть какой-то трюк, который я пропускаю?

Ответы [ 4 ]

5 голосов
/ 06 сентября 2011

Я думаю, что ваша основная проблема в том, что вы делаете catch(...) и переводите в std::runtime_error, тем самым теряя всю информацию о типах из исходного исключения.Вы должны просто перебросить с throw().

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

Вы правы, что существует потенциальная проблема, если вы создаете новый строковый объект для созданияисключение, но если вы хотите отформатировать сообщение с контекстом, этого вообще нельзя избежать.Обратите внимание, что все стандартные объекты исключений имеют конструктор const char* (по состоянию на последнюю неделю), поэтому, если у вас есть const char*, который вы хотите использовать, вам не нужно создавать новый объект std::string.

std::runtime_error должен скопировать свой аргумент, но не обязательно как новый строковый объект.Может быть область статически выделенной памяти, которой может соответствовать содержимое ее аргумента.Он должен удовлетворять только требованиям what(), для которых требуется только возврат const char *, ему не нужно хранить объект std::string.

3 голосов
/ 06 сентября 2011

std::runtime_error предназначен для обработки обычных ошибок времени выполнения, а не нехватки памяти или других подобных критических исключений.Базовый класс std::exception делает , а не делает все, что может бросить;и не std::bad_alloc.И очевидно, что преобразование std::bad_alloc в исключение, которое требует динамического выделения для работы, является плохой идеей.

3 голосов
/ 06 сентября 2011

Это было бы бросающим выражением, которое само по себе также выбрасывает: не вызовет ли это std :: terminate?

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

НО ПОДОЖДИТЕ, это тоже не сработает, не так ли?Потому что runtime_error собирается скопировать свой аргумент, который может также генерировать ...

Классы исключений с конструкторами-копирующими объектами являются такими же злыми, как и деструкторы с классами.С этим ничего нельзя поделать.

1 голос
/ 06 сентября 2011

Первое, что вы хотите сделать, если у вас возникнет исключение bad_alloc , потому что у вас недостаточно памяти?

Я бы сказал, в классической программе C ++вы бы хотели, чтобы программа как-то пыталась сообщить вам, что произошло, а затем завершается.

В классической программе на C ++ вы бы позволили исключению bad_alloc распространиться на основной разделпрограммы.Main будет содержать несколько вариантов try / catch:

int main()
{
   try
   {
      // your program starts
   }
   catch( const std::exception & e )
   {
       std::cerr << "huho something happened" << e.what() << std::endl;
   }
   catch( ... )
   {
       std::cerr << "huho..err..what?" << std::endl;
   }
}

вы будете использовать catch (...) только внутри main и в начальных функциях потоков.В отличие от некоторых других языков, таких как Java, вы не должны отлавливать все возможные исключения локально.Вы просто позволяете им распространяться до тех пор, пока не поймаете их там, где вы хотели.

Теперь, если у вас есть код, который должен проверять std :: bad_alloc , вам нужно только catch (const std:: bad_alloc &) локально.И там, возможно, стоит сделать что-то еще, а не просто выбросить другое исключение.

Я обнаружил в языке программирования C ++ §14.10 также, что механизм обработки исключений C ++ сохраняет немного памяти для хранения исключений., так что создание исключения из стандартной библиотеки не приведет к исключению само по себе.Конечно, можно также позволить механизму обработки исключений исчерпать память, если вы действительно кодируете что-то извращенное.

Итак, подведем итог, если вы ничего не делаете и допускаете большие исключения, такие как bad_alloc хорошо распространяйся там, где хочешь их поймать, по-моему, ты в безопасности.И вы не должны использовать catch (...) или catch (const std :: exception &) где угодно, кроме основной функции и начальных функций потоков.

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

...