- •И. А. Андрианов, д. В. Кочкин, с. Ю. Ржеуцкая
- •Учебное пособие
- •Оглавление
- •1. Основы языка 8
- •1.2.2 Простые типы данных 13
- •6. Паттерны проектирования 159
- •7. Антипаттерны 211
- •2. Работа с памятью 73
- •3. Основы объектно-ориентированного программирования 87
- •4.Обработка исключений 114
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов 130
- •9. Методы отладки и оптимизации кода 242
- •1. Основы языка
- •1.1.2 Понятие проекта
- •1.2 Простые типы данных
- •1.2.1 Понятие типа
- •1.2.2 Простые типы данных
- •1.2.3 Внутреннее представление простых типов
- •1.2.4 Ключевое слово typedef. Тип size_t
- •1.3 Константы и переменные
- •1.3.1 Литералы
- •1. Числовые константы:
- •2. Символьные константы:
- •1.3.2 Переменные
- •1.3.3 Описание переменных
- •1.4. Выражения. Преобразование типов
- •1.4.1 Операнды и операции
- •1.4.2 Приоритет операций
- •1.4.3 Преобразование типов
- •1.5 Ветвления и циклы
- •1.5.2 Циклы
- •1.6 Массивы, строки
- •1.6.1 Основные понятия
- •1.6.2 Встроенные массивы
- •1.6.3 Cтроки. Обработка строк с завершающим нулём
- •1.7 Указатели и ссылки. Связь указателей и массивов. Библиотека cstring
- •1.7.1 Понятия указателя и ссылки
- •1.7.2 Связь между массивами и указателями
- •1.7.3 Библиотека cstring
- •1.8 Использование типов vector и string
- •1.8.1 Шаблонный класс vector
- •1.8.2 Класс string
- •1.9 Структуры и объединения. Битовые поля
- •1.10.1 Понятие функции
- •1.10.2 Описание функции и прототип функции
- •1.11 Параметры функции. Способы передачи параметров
- •1.11.1 Параметры функции и глобальные переменные
- •1.11.2 Способы передачи параметров в функцию
- •1.11.3 Передача массивов в функцию
- •1.11.4 Параметры-константы
- •1.11.5 Значения параметров по умолчанию
- •1.12.1 Указатель на функцию
- •1.12.2 Функции с переменным числом параметров
- •1.12.3 Перегрузка функций
- •1.12.4 Встроенные (inline) функции
- •1.13 Рекурсивные функции
- •1.14 Пространства имён
- •1.15 Директивы препроцессора. Макросы
- •2. Работа с памятью
- •2.1 Управление выделением и освобождением памяти
- •2.1.1 Статическое и динамическое выделение памяти
- •2.1.2 Способы динамического выделения и освобождения памяти
- •2.2 Динамические структуры данных
- •2.2.1 Основные понятия
- •2.2.2 Примеры реализации динамических структур на основе указателей
- •3. Основы объектно-ориентированного программирования
- •3.1 Основные понятия ооп
- •3.2.1 Описание класса
- •3.2.2 Область видимости элементов класса. Инкапсуляция
- •3.2.3 Первые примеры
- •3.3. Конструкторы и деструкторы.
- •3.4 Указатель this
- •3.5 Перегрузка операций
- •3.6 Дружественные функции и классы
- •3.7 Статические элементы класса
- •3.8 Наследование и полиморфизм
- •3.8.1. Основные понятия
- •3.8.2 Одиночное наследование
- •3.8.3 Множественное наследование
- •3.8.4 Конструкторы и деструкторы классов-потомков
- •3.9. Полиморфизм при наследовании классов
- •3.9.1 Механизмы раннего и позднего связывания
- •3.9.2 Абстрактные классы
- •4.Обработка исключений
- •4.1 Основные понятия
- •4.2 Перехват исключений
- •4.3 Поиск обработчика исключений. Раскрутка стека.
- •4.4 Повторное возбуждение исключений
- •4.5 "Аппаратные" и "программные" исключения
- •4.6 Стандартные классы исключений
- •4.7 Спецификация исключений, возбуждаемых функцией
- •4.8 Исключения в конструкторах при наследовании
- •4.9. Исключения в деструкторах
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов
- •5.1 Шаблонные функции
- •5.2 Шаблонные классы
- •5.3 Специализация шаблонов
- •5.4 Шаблонные параметры шаблонов
- •5.6 Использование шаблонов для вычислений на этапе компиляции
- •5.5 Разработка шаблонных классов с настраиваемой функциональностью
- •5.7 Библиотека стандартных шаблонов (stl) – основные понятия
- •5.8 Последовательные контейнеры. Итераторы
- •5.9. Адаптеры контейнеров
- •5.10 Ассоциативные контейнеры
- •5.11 Алгоритмы
- •6. Паттерны проектирования
- •6.1 Порождающие шаблоны
- •6.2 Структурные шаблоны
- •6.3 Шаблоны поведения
- •6.4 Шаблон "фабричный метод" (Factory method)
- •6.5 Шаблон "одиночка" (Singleton)
- •6.6 Шаблон "итератор" (Iterator)
- •6.7 Шаблон "наблюдатель" (Observer)
- •6.8 Шаблон "пул объектов" (Object pool)
- •6.9 Шаблон "команда" (Command)
- •6. 10 Шаблон "посетитель" (Visitor)
- •6.11 Дополнительные задания
- •6.11.1 Шаблон Iterator
- •6.11.2 Шаблон Observer
- •6.11.3 Шаблоны Command и Observer
- •6.11.5 Шаблон Visitor
- •6.11.5 Разработка класса − контейнера
- •6.11.6 Оценка производительности кода
- •7. Антипаттерны
- •7.1 Программирование методом копирования и вставки (Copy-Paste Programming)
- •7.2 Спагетти-код (Spaghetti code)
- •7.3 Магические числа (Magic numbers)
- •7.4 Бездумное комментирование
- •7.5 Жесткое кодирование (Hard code)
- •7.6 Мягкое кодирование (Soft code)
- •7.7 Золотой молоток (Golden hammer)
- •7.8 Слепая вера (Blind faith)
- •7.9 Ненужная сложность (Accidental complexity)
- •7.10 Божественный объект (God Object)
- •7.11 Лодочный якорь (Boat anchor)
- •7.12 Поток лавы (Lava flow)
- •7.13 Изобретение велосипеда (Reinventing the wheel)
- •7.14 Программирование перебором (Programming by permutation)
- •8.1 Выведение типов
- •8.2 Списки инициализации
- •8.3 Улучшение процесса инициализации объектов
- •8.4 Цикл for по коллекции
- •8.5 Лямбда-функции
- •8.6 Константа нулевого указателя nullptr
- •8.7 "Умные" указатели
- •9. Методы отладки и оптимизации кода
- •9.1 Отладка кода
- •9.1.1 Основные этапы отладки
- •9.1.2 Инструменты и приёмы отладки
- •9.2 Оптимизация кода
- •9.2.1 Рекомендации по выполнению оптимизации
- •9.2.2 Методики оптимизации кода
- •Заключение
- •Библиографический список
4.5 "Аппаратные" и "программные" исключения
До сих пор мы рассматривали исключения, которые можно условно назвать "программными" − они возбуждаются с помощью вызова оператора throw (неважно, написан ли он нами, или скрыт где-то внутри реализации используемых библиотечных функций). Возникает вопрос, а можно ли на языке C++ обрабатывать так называемые "аппаратные" исключения, источником которых являются события в процессоре и/или в операционной системе. Простейшим примером является деление целого числа на ноль. Будет ли корректно работать следующий код?
int x, a, b;
try { x = a / b;}
catch(…) {std::cout << "Деление на 0\n";}
Разберёмся, что происходит при делении двух целых чисел. Если делитель оказывается равным нулю, то внутри процессора происходит прерывание. Выполнение программы приостанавливается, и вызывается обработчик этого прерывания − специальная подпрограмма операционной системы. В результате операционная система узнаёт о том, что произошло, а также в каком именно процессе (работающей программе) это случилось. Вопрос заключается в том, может ли данная информация каким-то образом быть передана операционной системой нашему процессу. При этом ещё и должна выполниться конвертация этого события в определённый тип исключений языка C++.
Возможно ли это, зависит от конкретной операционной системы и компилятора. Например, в ОС Windows встроен механизм, который называется "структурная обработка исключений" (Structured Exception Handling, SEH). Поддержка таких исключений имеется в компиляторе Microsoft Visual C++ (по умолчанию она выключена, для включения нужно использовать ключ компиляции /EHa либо соответствующую настройку проекта). Более того, в MS Visual C++ даже имеются специализированные операторы try-except и try-finally для работы с SHE (поскольку данные операторы отсутствуют в стандарте языка С++, в данной книге мы их рассматривать не будем).
Заметим, что поддержка подобных исключений в настоящее время отсутствует в текущей версии компилятора GNU C++ для Windows (из пакета mingw). Однако, версия компилятора GNU C++ для ОС Linux похожий вид исключений поддерживает.
Таким образом, можно сделать вывод, что при написании переносимого кода, который должен компилироваться разными компиляторами под разные платформы, использовать "аппаратные" исключения в настоящее время не рекомендуется.
4.6 Стандартные классы исключений
Cтандартная библиотека языка C++ содержит следующую иерархию классов исключений. Корневой класс исключения называется std::exception, он объявлен в заголовочном файле exception. Данный класс содержит виртуальный метод what, возвращающий С-строку с описанием возникшего исключения. Классы, производные от exception, могут заместить метод what собственной версией.
От класса std::exception наследуется несколько классов: std::bad_alloc (ошибка выделения памяти), std::bad_cast (ошибка динамического преобразования типов), std::bad_typeid (применение оператора typeid к пустому указателю) и несколько других. Особо стоит рассмотреть два производных класса − std::logic_error (логическая ошибка) и std::runtime_error (ошибка времени выполнения).
Исключения типа std::logic_error предполагают нарушения внутренней логики программы − например, логических предусловий или инвариантов класса. Зачастую такие ошибки можно найти и предотвратить ещё до начала выполнения программы. В стандартной библиотеке определены несколько наследников класса std::logic_error:
• std::domain_error − ошибка домена. Пример: sqrt(-1).
• std::invalid_argument − некорректный аргумент. Пример: std::bitset<5> mybitset (std::string("01234")); Исключение возникнет из-за того, что строка- аргумент конструктора класса bitset может содержать лишь символы 0 или 1.
• std::length_error − ошибка длины. Пример: попытаемся установить размер вектора выше максимально допустимого размера: std::vector<int> v; v.resize(v.max_size()+1);
• std::out_of_range − выход за границы. Пример: std::vector<int> a(10); a.at(20) = 5;
Некоторые из этих исключений удобно использовать и в своих функциях, чтобы не создавать собственные классы исключений. Например, если функция получила аргумент с некорректным значением, можно возбудить исключение std::invalid_argument. Если значение аргумента выходит за пределы допустимого диапазона, можно возбудить исключение std::out_of_range. При попытке создать объект, длина которого превышает максимально возможную, можно возбудить исключение std::length_error.
Ещё одним наследником класса std::exception является класс std::runtime_error − исключение времени выполнения. Предполагается, что такие исключения достаточно сложно обнаружить заранее, до того как программа начала работать. В стандартной библиотеке имеются несколько наследников класса std::runtime_error − std::range_error, std::overflow_error, std::underflow_error, std::system_error. Лишь небольшое количество редко используемых функций стандартной библиотеки генерируют исключения данных классов, поэтому примеры мы не приводим. Обычные арифметические операции таких исключений не возбуждают. Однако, можно использовать данные исключения в своих программах − например, при разработке библиотеки по работе с простыми дробями может быть вполне уместно возбуждение исключений типов std::overflow_error или std::range_error.
Иногда может быть удобно создание своего класса − наследника от одного из стандартных классов исключений, например:
// Пример 4.9 - наследование исключения
class FileNotOpenedException : public std::runtime_error{
public:
FileNotOpenedException(const std::string &fileName)
: std::runtime_error ("Ошибка открытия файла " + fileName) {
}
// функция what будет возвращать строку,
// которую мы передали в конструктор предка
};
Здесь удобно то, что класс std::runtime_error содержит конструктор, принимающий в качестве параметра строку с описанием ошибки, а также реализацию функции what, возвращающую эту строку. В результате нет необходимости писать аналогичную реализацию "с нуля".
