Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КП - 2 часть - Лекция 2. Исключительные ситуаци...docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
342.41 Кб
Скачать

Кучук Сергей Александрович

Белорусский Государственный Университет Информатики и Радиоэлектроники

Конструирование программ

Лекции

2 Часть

Лекция 2 – Исключительные ситуации

План лекции

1 Приближение 3

1.1 Бросание(генерация) исключений 3

1.2 Пример 1. Поддержка инкапсуляции 4

1.3 Ловля исключений 7

2 Приближение 10

2.1 Развитие способов обработки ошибок 10

2.2 Преимущества использования исключений 15

2.3 Какие есть проблемы с исключениями в С++ 15

2.4 Пример 2. Исключение и память, проброс 17

2.5 Исключения в деструкторах 18

3 Приближение 18

3.1 Спецификация исключений 18

3.2 Обработка неожидаемых исключений 18

3.3 Отладка и исключения в Visual Studio. Stack trace 18

3.4 Как развивался концепт с исключениями дальше? 21

3.4.1 finally 21

3.4.2 Сборщик мусора 22

3.4.3 «Умные указатели» в С++ 23

3.5 Общие замечания по созданию исключительных ситуаций 23

3.6 Код всегда должен писаться, чтобы быть устойчивым к исключениям [1] 24

3.7 C++ 11 24

Видео по лекциям:

http://www.youtube.com/watch?v=JT7I2kMXw8Y

http://www.youtube.com/watch?v=pOle3zFDm2w

1 Приближение

Идея за исключениями: отделить код по обработке ошибок от кода по обнаружению ошибок. Современные приложения многоуровневы [1]:

Логика приложения

Уровень кода

Уровень кода

Уровень кода

Уровень кода

Низкоуровневая реализация.

Как обработать ошибку низкого уровня (не удается открыть файл, места нету)? Может показать пользователю окошко с сообщением об ошибке, если это настольное приложение, может записать сообщение в лог, если это сервисный процесс... Идея: низкоуровневая реализация НЕ ДОЛЖНА ЗНАТЬ КАК ОШИБКУ ОБРАБОТАЮТ. Как обработать ошибку знает логика приложения. До появления исключений протягивали коды ошибок или выставляли глобальные переменные-флаги ошибок.

Появляются исключения.

1.1 Бросание(генерация) исключений

Исключения объявлены в <exception> заголовочном файле.

Чтобы бросить исключения, нужно вызвать в коде какую-то такую конструкцию:

throw std::exception("Что не так.");

Т.е. мы создали временный объект, затем кинули его.

Практика: исключения бросаются только по значению.

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

Пример, раскручивание стека:

#include "stdafx.h"

#include <iostream>

#include <exception>

#include <string>

using namespace std;

void C()

{

cout << "Before C\n";

throw exception("Exception in the middle of C!");

cout << "After C\n";

}

void B()

{

cout << "Before B\n";

C();

cout << "After B\n";

}

void A()

{

cout << "Before A\n";

B();

cout << "After A\n";

}

int _tmain(int argc, _TCHAR* argv[])

{

try

{

A();

}

catch (const std::exception e)

{

cerr << e.what() << endl;

getchar();

return 1;

}

getchar();

return 0;

}

Вывод

1.2 Пример 1. Поддержка инкапсуляции

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

Давайте рассмотрим пример:

// Person.h

#ifndef __PERSON_H__

#define __PERSON_H__

#include <string>

class Person

{

public:

const std::string& GetName() const;

int GetAge() const;

void SetName(const std::string& name);

void SetAge(int age);

Person();

Person(const std::string& name, int age);

private:

std::string m_name;

int m_age;

};

#endif

// Person.cpp

#include "stdafx.h"

#include "Person.h"

const std::string& Person::GetName() const

{

return m_name;

}

int Person::GetAge() const

{

return m_age;

}

void Person::SetName(const std::string& name)

{

if (name.empty())

throw std::exception("'name' is empty.");

m_name = name;

}

void Person::SetAge(int age)

{

if (age < 0)

throw std::exception("'age' is out of bounds.");

m_age = age;

}

Person::Person()

{

SetName("John Galt");

SetAge(120);

}

Person::Person(const std::string& name, int age)

{

SetName(name);

SetAge(age);

}

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

Итак,

Исключение бросают по значению, как в примере выше, а не по адресу (throw new std::exception("what’s wrong")). То, что кидается в качестве исключения, должно быть копируемым, поскольку бросается копия. Бросать адреса – указатель – плохо, поскольку не ясно, кто будет отвечать за очистку памяти. Ссылку не стоит бросать.

Что произойдет, если объекты будут в автоматической памяти?

string localObject("Some string");

throw std::exception("Something is going bad!");

Эти объекты удалятся (выйдем за пределы видимости, когда throw произойдет).

В качестве исключения может быть брошен не только экземпляр класса исключения, как в примере выше, но и другие типы данных (throw 13 / throw "Travolta"). Но использовать этот пережиток прошлого – возможность бросать другие типы данных – не стоит. Бросать стоит только экземпляры класса исключение или наследников от него. Какие наследники есть у класса исключение?

(картинка взята с http://cs.stmarys.ca/~porter/csc/ref/cpp_standlib.html)

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

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

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