Где я могу найти информацию о методах исключения C ++ / STL? - PullRequest
7 голосов
/ 13 сентября 2011

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

В основном, скажем, у вас есть:

class X {
string m_str;
X() : m_str("foo")//what if this throws?
{
    ifstream b("a.in")//what if this throws?
}

Ипосле просмотра всех статей, которые я смог найти, я все еще не представляю, как это можно сделать.

Скажем, у меня есть такой код:

{
    ...
    X myInstanceOfClassX;
    ...
}

Должен ли я обернутькод в catch(exception &)?И если я сделаю это, string и ifstream гарантируют надежную гарантию, что никакие ресурсы не просочились и ничего не осталось наполовину открытым?

Кроме того, если мой класс выбрасывает myexception, то естьполученный из исключения, catch(exception &), кажется, пропустил это.Так что у меня остается catch(...), какое IIRC отлавливает нарушение доступа?Есть ли другой способ?

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

А что, если приведенный выше код был бы вызван не из конструктора, а из обычной функции void foo(), какие исключения я должен перехватить?outofmemory_something, файл notfound_something?Где я могу найти определения того, что могут генерировать объекты STL?Являются ли они конкретными для реализации?

Где находится авторитетный источник, где я мог бы прояснить все свои сомнения и вопросы по этой теме?

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

Ответы [ 5 ]

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

Если любой из этих бросков

class X {
string m_str;
X() : m_str("foo")//what if this throws?
{
    ifstream b("a.in")//what if this throws?
}

Тогда созданный вами объект не будет существовать.
Если в конструктор объекта выдается исключение, то все полностью созданные члены уничтожаются (используя их деструктор), и память для объекта возвращается в систему. Таким образом, любые члены, которые не полностью построены в точке выброса, не будут уничтожены (так как они не были созданы).

  • Если m_str () выбрасывает в списке инициализатора, объект никогда не будет существовать.
  • Если ifstream выбрасывает тело, m_str уничтожается и объект никогда не будет существовать.

Должен ли я обернуть код в catch (исключение &)? И если я сделаю это, гарантирует ли string и ifstream надежную гарантию, что никакие ресурсы не просочились и ничего не осталось наполовину открытым?

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

В приведенном выше вы гарантированно нет утечек или открытых ресурсов.

Кроме того, если мой класс выбрасывает myexception, полученное из исключения, catch (exception &), кажется, пропускает его. Так что у меня остается с catch (...), какой IIRC ловит нарушение доступа? Есть ли другой способ?

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

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

Вероятно, лучший вариант и, как правило, неплохой совет.

А что если код выше был бы вызван не из конструктора, а из обычной функции void foo (), какие исключения я должен перехватить? outofmemory_something, файл notfound_something? Где я могу найти определения того, что могут генерировать объекты STL? Они специфичны для реализации?

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

Где находится авторитетный источник, где я мог бы прояснить все мои сомнения и вопросы по этой теме?

Ваш вопрос настолько разнообразен, что это сложно.
Я мог бы рекомендовать "Исключительный C ++" Хербом Саттером .

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

Вы не правы там. Исключения намного проще. Вы просто, кажется, слишком обдумали это и запутались. Это не означает, что коды ошибок не имеют своего места.

Если что-то пойдет не так and you can not fix it locally, тогда выведите исключение. Все классы в стандарте разработаны с учетом исключений и будут вести себя правильно. Так что это просто оставляет ваши уроки.

Эмпирические правила: (для ваших объектов)

  • Убедитесь, что ваши классы убираются в деструкторе
  • Если ваш объект содержит ресурсы, убедитесь, что «соблюдается правило 3»
  • Никогда не иметь более одного ресурса на объект.
    Примечание: у вас может быть несколько вещей, таких как std :: string или std :: ifstream, так как они управляют ресурсом (каждый из них контролирует один ресурс, поэтому ваш класс не контролирует ресурс). Ресурс (в этом контексте) - это то, что вы должны вручную создать / уничтожить.

Вот и все, все остальное работает автоматически.

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

Каждая функция имеет предусловие и постусловие.Правильное время для исключения - это когда постусловия не могут быть выполнены.Нет другого правильного времени.

Существует два особых случая.

  • Постусловием для конструктора является наличие действительного объекта ,следовательно, бросание - это только разумный способ сообщить об ошибке.Если у вас есть что-то вроде теста Foo::is_ok(), то у вас есть действительный объект, представляющий недопустимое состояние.

  • Постусловием для деструктора является отсутствие объекта, поэтому выбрасывание никогда - разумный способ сообщить об ошибке.Если у вас есть что-то хитрое в конце жизни объекта, сделайте это как отдельный Foo::commit() вызов функции-члена.

Помимо этого у вас есть варианты, и это вопросвкус.

Например,

  • std::vector::operator[] не проверяет предварительные условия и составляет noexcept(true), но
  • std::vector::at() проверяет и бросает.

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

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

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

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

Я склонен предпочесть исключения C ++ кодам ошибок, даже если они считаются «потоком управления», потому что мне не нужно добавлять проверки везде. Но это спорный вопрос вкуса. Однако для конструкторов у вас нет выбора.

Как только конструктор выдает исключение, все подобъекты, которые были инициализированы, уничтожаются, и если объект был создан с помощью operator new, вызывается соответствующий operator delete.

Обратите внимание, что когда конструктор выдает объект, его нельзя использовать:

my_class a; // If this throws, everything past this line is not accessible.
            // Therefore, you cannot use a.

или

my_class* b;

try
{
    b = new my_class; // If this throws, ...
}
catch (...)
{
    // b has undefined state here (but no memory is leaked)
}

Так что, если вы используете только надлежащие объекты RAII, вы в безопасности и вам нечего делать, кроме как разрешить распространение исключения. Однако если вы вручную извлекаете одноразовый ресурс, возможно, вам придется его очистить и повторно выдать исключение:

template <typename T>
struct my_vector
{
    // This is why it is not advisable to roll your own vector.
    my_vector(size_t n, const T& x)
    {
        begin = static_cast<T*>(custom_allocator(n * sizeof(T)));
        end = begin + n;

        size_t k = 0;
        try
        {
            // This can throw...
            for (; k != n; k++) new(begin + k) T(x); 
        }
        catch (...)
        {
            // ... so destroy everything and bail out
            while (--k) (begin + k)->~T();
            custom_deallocator(begin);
            throw;
        }
    }

private:
    T* begin;
    T* end;
};

но это должно быть довольно редко, если вы используете правильные объекты RAII (быстрый grep из моей текущей кодовой базы показывает сотни throw, но только два catch).

Гарантии исключения из стандартной библиотеки можно найти в стандартном документе ISO (за это нужно заплатить небольшую плату).

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

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

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

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

0 голосов
/ 13 сентября 2011

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

Имейте в виду, что исключение не выдается, если, например, файл не может быть открыт - это просто приведет к тому, что для потока будет установлено состояние сбоя.

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