- •Часть I
- •1. Начинаем
- •1.1. Решение задачи
- •1.2.1. Порядок выполнения инструкций
- •1.3. Директивы препроцессора
- •1.4. Немного о комментариях
- •1.5. Первый взгляд на ввод/вывод
- •1.5.1. Файловый ввод/вывод
- •2.1. Встроенный тип данных “массив”
- •2.2. Динамическое выделение памяти и указатели
- •2.3. Объектный подход
- •2.4. Объектно-ориентированный подход
- •2.5. Использование шаблонов
- •2.6. Использование исключений
- •2.7. Использование пространства имен
- •2.8. Стандартный массив – это вектор
- •Часть II
- •3.1. Литералы
- •3.2. Переменные
- •3.2.1. Что такое переменная
- •3.2.2. Имя переменной
- •3.2.3. Определение объекта
- •3.3. Указатели
- •3.4. Строковые типы
- •3.4.1. Встроенный строковый тип
- •3.4.2. Класс string
- •3.5. Спецификатор const
- •3.6. Ссылочный тип
- •3.8. Перечисления
- •3.9. Тип “массив”
- •3.9.1. Многомерные массивы
- •3.9.2. Взаимосвязь массивов и указателей
- •3.10. Класс vector
- •3.11. Класс complex
- •3.12. Директива typedef
- •3.13. Спецификатор volatile
- •3.14. Класс pair
- •3.15. Типы классов
- •4. Выражения
- •4.1. Что такое выражение?
- •4.2. Арифметические операции
- •4.3. Операции сравнения и логические операции
- •4.4. Операции присваивания
- •4.5. Операции инкремента и декремента
- •4.6. Операции с комплексными числами
- •4.7. Условное выражение
- •4.8. Оператор sizeof
- •4.9. Операторы new и delete
- •4.10. Оператор “запятая”
- •4.11. Побитовые операторы
- •4.12. Класс bitset
- •4.13. Приоритеты
- •4.14. Преобразования типов
- •4.14.1. Неявное преобразование типов
- •4.14.2. Арифметические преобразования типов
- •4.14.3. Явное преобразование типов
- •4.14.4. Устаревшая форма явного преобразования
- •4.15. Пример: реализация класса Stack
- •5. Инструкции
- •5.1. Простые и составные инструкции
- •5.2. Инструкции объявления
- •5.3. Инструкция if
- •5.4. Инструкция switch
- •5.5. Инструкция цикла for
- •5.6. Инструкция while
- •5.8. Инструкция do while
- •5.8. Инструкция break
- •5.9. Инструкция continue
- •5.10. Инструкция goto
- •5.11. Пример связанного списка
- •5.11.1. Обобщенный список
- •6. Абстрактные контейнерные типы
- •6.1. Система текстового поиска
- •6.2. Вектор или список?
- •6.3. Как растет вектор?
- •6.4. Как определить последовательный контейнер?
- •6.5. Итераторы
- •6.6. Операции с последовательными контейнерами
- •6.6.1. Удаление
- •6.6.2. Присваивание и обмен
- •6.6.3. Обобщенные алгоритмы
- •6.7. Читаем текстовый файл
- •6.8. Выделяем слова в строке
- •6.9. Обрабатываем знаки препинания
- •6.10. Приводим слова к стандартной форме
- •6.11. Дополнительные операции со строками
- •6.12. Строим отображение позиций слов
- •6.12.1. Определение объекта map и заполнение его элементами
- •6.12.2. Поиск и извлечение элемента отображения
- •6.12.3. Навигация по элементам отображения
- •6.12.4. Словарь
- •6.12.5. Удаление элементов map
- •6.13. Построение набора стоп-слов
- •6.13.1. Определение объекта set и заполнение его элементами
- •6.13.2. Поиск элемента
- •6.13.3. Навигация по множеству
- •6.14. Окончательная программа
- •6.15. Контейнеры multimap и multiset
- •6.16. Стек
- •6.17. Очередь и очередь с приоритетами
- •6.18. Вернемся в классу iStack
- •Часть III
- •7. Функции
- •7.1. Введение
- •7.2. Прототип функции
- •7.2.1. Тип возвращаемого функцией значения
- •7.2.2. Список параметров функции
- •7.2.3. Проверка типов формальных параметров
- •7.3. Передача аргументов
- •7.3.1. Параметры-ссылки
- •7.3.2. Параметры-ссылки и параметры-указатели
- •7.3.3. Параметры-массивы
- •7.3.4. Абстрактные контейнерные типы в качестве параметров
- •7.3.5. Значения параметров по умолчанию
- •7.3.6. Многоточие
- •7.4. Возврат значения
- •7.4.1. Передача данных через параметры и через глобальные объекты
- •7.5. Рекурсия
- •7.6. Встроенные функции
- •7.7. Директива связывания extern "c" a
- •7.8. Функция main(): разбор параметров командной строки
- •7.8.1. Класс для обработки параметров командной строки
- •7.9. Указатели на функции
- •7.9.1. Тип указателя на функцию
- •7.9.2. Инициализация и присваивание
- •7.9.3. Вызов
- •7.9.4. Массивы указателей на функции
- •7.9.5. Параметры и тип возврата
- •7.9.6. Указатели на функции, объявленные как extern "c"
- •8. Область видимости и время жизни
- •8.1. Область видимости
- •8.1.1. Локальная область видимости
- •8.2. Глобальные объекты и функции
- •8.2.1. Объявления и определения
- •8.2.2. Сопоставление объявлений в разных файлах
- •8.2.3. Несколько слов о заголовочных файлах
- •8.3. Локальные объекты
- •8.3.1. Автоматические объекты
- •8.3.2. Регистровые автоматические объекты
- •8.3.3. Статические локальные объекты
- •8.4. Динамически размещаемые объекты
- •8.4.1. Динамическое создание и уничтожение единичных объектов
- •8.4.2. Шаблон auto_ptr а
- •8.4.3. Динамическое создание и уничтожение массивов
- •8.4.4. Динамическое создание и уничтожение константных объектов
- •8.4.5. Оператор размещения new а
- •8.5. Определения пространства имен а
- •8.5.1. Определения пространства имен
- •8.5.2. Оператор разрешения области видимости
- •8.5.3. Вложенные пространства имен
- •8.5.4. Определение члена пространства имен
- •8.5.5. Поо и члены пространства имен
- •8.5.6. Безымянные пространства имен
- •8.6. Использование членов пространства имен а
- •8.6.1. Псевдонимы пространства имен
- •8.6.2. Using-объявления
- •8.6.3. Using-директивы
- •8.6.4. Стандартное пространство имен std
- •9. Перегруженные функции
- •9.1. Объявления перегруженных функций
- •9.1.1. Зачем нужно перегружать имя функции
- •9.1.2. Как перегрузить имя функции
- •9.1.3. Когда не надо перегружать имя функции
- •9.1.4. Перегрузка и область видимости a
- •9.1.5. Директива extern "c" и перегруженные функции a
- •9.1.6. Указатели на перегруженные функции a
- •9.1.7. Безопасное связывание a
- •9.2. Три шага разрешения перегрузки
- •9.3. Преобразования типов аргументов a
- •9.3.1. Подробнее о точном соответствии
- •9.3.2. Подробнее о расширении типов
- •9.3.3. Подробнее о стандартном преобразовании
- •9.3.4. Ссылки
- •9.4. Детали разрешения перегрузки функций
- •9.4.1. Функции-кандидаты
- •9.4.2. Устоявшие функции
- •9.4.3. Наилучшая из устоявших функция
- •9.4.4. Аргументы со значениями по умолчанию
- •10. Шаблоны функций
- •10.1. Определение шаблона функции
- •10.2. Конкретизация шаблона функции
- •10.3. Вывод аргументов шаблона а
- •10.4. Явное задание аргументов шаблона a
- •10.5. Модели компиляции шаблонов а
- •10.5.1. Модель компиляции с включением
- •10.5.2. Модель компиляции с разделением
- •10.5.3. Явные объявления конкретизации
- •10.6. Явная специализация шаблона а
- •10.7. Перегрузка шаблонов функций а
- •10.8. Разрешение перегрузки при конкретизации a
- •10.9. Разрешение имен в определениях шаблонов а
- •10.10. Пространства имен и шаблоны функций а
- •10.11. Пример шаблона функции
- •11. Обработка исключений
- •11.1. Возбуждение исключения
- •11.3. Перехват исключений
- •11.3.1. Объекты-исключения
- •11.3.2. Раскрутка стека
- •11.3.3. Повторное возбуждение исключения
- •11.3.4. Перехват всех исключений
- •11.4. Спецификации исключений
- •11.4.1. Спецификации исключений и указатели на функции
- •11.5. Исключения и вопросы проектирования
- •12. Обобщенные алгоритмы
- •12.1. Краткий обзор
- •12.2. Использование обобщенных алгоритмов
- •12.3. Объекты-функции
- •12.3.1. Предопределенные объекты-функции
- •12.3.2. Арифметические объекты-функции
- •12.3.3. Сравнительные объекты-функции
- •12.3.4. Логические объекты-функции
- •12.3.5. Адаптеры функций для объектов-функций
- •12.3.6. Реализация объекта-функции
- •12.4. Еще раз об итераторах
- •12.4.1. Итераторы вставки
- •12.4.2. Обратные итераторы
- •12.4.3. Потоковые итераторы
- •12.4.4. Итератор istream_iterator
- •12.4.5. Итератор ostream_iterator
- •12.4.6. Пять категорий итераторов
- •12.5. Обобщенные алгоритмы
- •12.5.1. Алгоритмы поиска
- •12.5.2. Алгоритмы сортировки и упорядочения
- •12.5.3. Алгоритмы удаления и подстановки
- •12.5.4. Алгоритмы перестановки
- •12.5.9. Алгоритмы работы с хипом
- •12.6. Когда нельзя использовать обобщенные алгоритмы
- •12.6.1. Операция list_merge()
- •12.6.2. Операция list::remove()
- •12.6.3. Операция list::remove_if()
- •12.6.4. Операция list::reverse()
- •12.6.5. Операция list::sort()
- •12.6.6. Операция list::splice()
- •12.6.7. Операция list::unique()
- •Часть IV
- •13. Классы
- •13.1. Определение класса
- •13.1.1. Данные-члены
- •13.1.2. Функции-члены
- •13.1.3. Доступ к членам
- •13.1.4. Друзья
- •13.1.5. Объявление и определение класса
- •13.2. Объекты классов
- •13.3. Функции-члены класса
- •13.3.1. Когда использовать встроенные функции-члены
- •13.3.2. Доступ к членам класса
- •13.3.3. Закрытые и открытые функции-члены
- •13.3.4. Специальные функции-члены
- •13.3.5. Функции-члены со спецификаторами const и volatile
- •13.3.6. Объявление mutable
- •13.4. Неявный указатель this
- •13.4.1. Когда использовать указатель this
- •13.5. Статические члены класса
- •13.5.1. Статические функции-члены
- •13.6. Указатель на член класса
- •13.6.1. Тип члена класса
- •13.6.2. Работа с указателями на члены класса
- •13.6.3. Указатели на статические члены класса
- •13.7. Объединение – класс, экономящий память
- •13.8. Битовое поле – член, экономящий память
- •13.9. Область видимости класса a
- •13.9.1. Разрешение имен в области видимости класса
- •13.10. Вложенные классы a
- •13.10.1. Разрешение имен в области видимости вложенного класса
- •13.11. Классы как члены пространства имен a
- •13.12. Локальные классы a
- •14. Инициализация, присваивание и уничтожение класса
- •14.1. Инициализация класса
- •14.2. Конструктор класса
- •14.2.1. Конструктор по умолчанию
- •14.2.2. Ограничение прав на создание объекта
- •14.2.3. Копирующий конструктор
- •14.3. Деструктор класса
- •14.3.1. Явный вызов деструктора
- •14.3.2. Опасность увеличения размера программы
- •14.4. Массивы и векторы объектов
- •14.4.1. Инициализация массива, распределенного из хипа a
- •14.4.2. Вектор объектов
- •14.5. Список инициализации членов
- •14.6. Почленная инициализация a
- •14.6.1. Инициализация члена, являющегося объектом класса
- •14.7. Почленное присваивание a
- •14.8. Соображения эффективности a
- •15. Перегруженные операторы и определенные пользователем преобразования
- •15.1. Перегрузка операторов
- •15.1.1. Члены и не члены класса
- •15.1.2. Имена перегруженных операторов
- •15.1.3. Разработка перегруженных операторов
- •15.2. Друзья
- •15.4. Оператор взятия индекса
- •15.5. Оператор вызова функции
- •15.6. Оператор “стрелка”
- •15.7. Операторы инкремента и декремента
- •15.8. Операторы new и delete
- •15.8.1. Операторы new[ ] и delete [ ]
- •15.8.2. Оператор размещения new() и оператор delete()
- •15.9. Определенные пользователем преобразования
- •15.9.1. Конвертеры
- •15.9.2. Конструктор как конвертер
- •15.10. Выбор преобразования a
- •15.10.1. Еще раз о разрешении перегрузки функций
- •15.10.2. Функции-кандидаты
- •15.10.3. Функции-кандидаты для вызова функции в области видимости класса
- •15.10.4. Ранжирование последовательностей определенных пользователем преобразований
- •15.11. Разрешение перегрузки и функции-члены a
- •15.11.1. Объявления перегруженных функций-членов
- •15.11.2. Функции-кандидаты
- •15.11.3. Устоявшие функции
- •15.12. Разрешение перегрузки и операторы a
- •15.12.1. Операторные функции-кандидаты
- •15.12.2. Устоявшие функции
- •15.12.3. Неоднозначность
- •16. Шаблоны классов
- •16.1. Определение шаблона класса
- •16.1.1. Определения шаблонов классов Queue и QueueItem
- •16.2. Конкретизация шаблона класса
- •16.2.1. Аргументы шаблона для параметров-констант
- •16.3. Функции-члены шаблонов классов
- •16.3.1. Функции-члены шаблонов Queue и QueueItem
- •16.4. Объявления друзей в шаблонах классов
- •16.4.1. Объявления друзей в шаблонах Queue и QueueItem
- •16.5. Статические члены шаблонов класса
- •16.6. Вложенные типы шаблонов классов
- •16.7. Шаблоны-члены
- •16.8. Шаблоны классов и модель компиляции a
- •16.8.1. Модель компиляции с включением
- •16.8.2. Модель компиляции с разделением
- •16.8.3. Явные объявления конкретизации
- •16.9. Специализации шаблонов классов a
- •16.10. Частичные специализации шаблонов классов a
- •16.11. Разрешение имен в шаблонах классов a
- •16.12. Пространства имен и шаблоны классов
- •16.13. Шаблон класса Array
- •Часть V
- •17. Наследование и подтипизация классов
- •17.1. Определение иерархии классов
- •17.1.1. Объектно-ориентированное проектирование
- •17.2. Идентификация членов иерархии
- •17.2.1. Определение базового класса
- •17.2.2. Определение производных классов
- •17.2.3. Резюме
- •17.3. Доступ к членам базового класса
- •17.4. Конструирование базового и производного классов
- •17.4.1. Конструктор базового класса
- •17.4.2. Конструктор производного класса
- •17.4.3. Альтернативная иерархия классов
- •17.4.4. Отложенное обнаружение ошибок
- •17.4.5. Деструкторы
- •17.5. Виртуальные функции в базовом и производном классах
- •17.5.1. Виртуальный ввод/вывод
- •17.5.2. Чисто виртуальные функции
- •17.5.3. Статический вызов виртуальной функции
- •17.5.4. Виртуальные функции и аргументы по умолчанию
- •17.5.5. Виртуальные деструкторы
- •17.5.6. Виртуальная функция eval()
- •17.5.7. Почти виртуальный оператор new
- •17.5.8. Виртуальные функции, конструкторы и деструкторы
- •17.6. Почленная инициализация и присваивание a
- •17.7. Управляющий класс UserQuery
- •17.7.1. Определение класса UserQuery
- •17.8. Соберем все вместе
- •18. Множественное и виртуальное наследование
- •18.1. Готовим сцену
- •18.2. Множественное наследование
- •18.3. Открытое, закрытое и защищенное наследование
- •18.3.1. Наследование и композиция
- •18.3.2. Открытие отдельных членов
- •18.3.3. Защищенное наследование
- •18.3.4. Композиция объектов
- •18.4. Область видимости класса и наследование
- •18.4.1. Область видимости класса при множественном наследовании
- •18.5. Виртуальное наследование a
- •18.5.1. Объявление виртуального базового класса
- •18.5.2. Специальная семантика инициализации
- •18.5.3. Порядок вызова конструкторов и деструкторов
- •18.5.4. Видимость членов виртуального базового класса
- •18.6. Пример множественного виртуального наследования a
- •18.6.1. Порождение класса, контролирующего выход за границы массива
- •18.6.2. Порождение класса отсортированного массива
- •18.6.3. Класс массива с множественным наследованием
- •19.1. Идентификация типов во время выполнения
- •19.1.1. Оператор dynamic_cast
- •19.1.2. Оператор typeid
- •19.1.3. Класс type_info
- •19.2. Исключения и наследование
- •19.2.1. Исключения, определенные как иерархии классов
- •19.2.2. Возбуждение исключения типа класса
- •19.2.3. Обработка исключения типа класса
- •19.2.4. Объекты-исключения и виртуальные функции
- •19.2.5. Раскрутка стека и вызов деструкторов
- •19.2.6. Спецификации исключений
- •19.2.7. Конструкторы и функциональные try-блоки
- •19.3. Разрешение перегрузки и наследование a
- •19.3.1. Функции-кандидаты
- •19.3.2. Устоявшие функции и последовательности пользовательских преобразований
- •19.3.3. Наилучшая из устоявших функций
- •20. Библиотека iostream
- •20.2. Ввод
- •20.2.1. Строковый ввод
- •20.3. Дополнительные операторы ввода/вывода
- •20.4. Перегрузка оператора вывода
- •20.5. Перегрузка оператора ввода
- •20.6. Файловый ввод/вывод
- •20.7. Состояния потока
- •20.8. Строковые потоки
- •20.9. Состояние формата
- •20.10. Сильно типизированная библиотека
- •21. Обобщенные алгоритмы в алфавитном порядке
- •Алгоритм accumulate()
- •Алгоритм adjacent_difference()
- •Алгоритм adjacent_find()
- •Алгоритм binary_search()
- •Алгоритм copy()
- •Алгоритм copy_backward()
- •Алгоритм count()
- •Алгоритм count_if()
- •Алгоритм equal()
- •Алгоритм equal_range()
- •Алгоритм fill()
- •Алгоритм fill_n()
- •Алгоритм find()
- •Алгоритм find_if()
- •Алгоритм find_end()
- •Алгоритм find_first_of()
- •Алгоритм for_each()
- •Алгоритм generate()
- •Алгоритм generate_n()
- •Алгоритм includes()
- •Алгоритм inner_product()
- •Алгоритм inplace_merge()
- •Алгоритм iter_swap()
- •Алгоритм lexicographical_compare()
- •Алгоритм lower_bound()
- •Алгоритм max()
- •Алгоритм max_element()
- •Алгоритм min()
- •Алгоритм min_element()
- •Алгоритм merge()
- •Алгоритм mismatch()
- •Алгоритм next_permutation()
- •Алгоритм nth_element()
- •Алгоритм partial_sort()
- •Алгоритм partial_sort_copy()
- •Алгоритм partial_sum()
- •Алгоритм partition()
- •Алгоритм prev_permutation()
- •Алгоритм random_shuffle()
- •Алгоритм remove()
- •Алгоритм remove_copy()
- •Алгоритм remove_if()
- •Алгоритм remove_copy_if()
- •Алгоритм replace()
- •Алгоритм replace_copy()
- •Алгоритм replace_if()
- •Алгоритм replace_copy_if()
- •Алгоритм reverse()
- •Алгоритм reverse_copy()
- •Алгоритм rotate()
- •Алгоритм rotate_copy()
- •Алгоритм search()
- •Алгоритм search_n()
- •Алгоритм set_difference()
- •Алгоритм set_intersection()
- •Алгоритм set_symmetric_difference()
- •Алгоритм set_union()
- •Алгоритм sort()
- •Алгоритм stable_partition()
- •Алгоритм stable_sort()
- •Алгоритм swap()
- •Алгоритм swap_ranges()
- •Алгоритм transform()
- •Алгоритм unique()
- •Алгоритм unique_copy()
- •Алгоритм upper_bound()
- •Алгоритмы для работы с хипом
- •Алгоритм make_heap()
- •Алгоритм pop_heap()
- •Алгоритм push_heap()
- •Алгоритм sort_heap()
8.4. Динамически размещаемые объекты
Время жизни глобальных и локальных объектов четко определено. Программист неспособен хоть как-то изменить его. Однако иногда необходимо иметь объекты, временем жизни которых можно управлять. Выделение памяти под них и ее освобождение зависят от действий выполняющейся программы. Например, можно отвести память под текст сообщения об ошибке только в том случае, если ошибка действительно имела место. Если программа выдает несколько таких сообщений, размер выделяемой строки будет разным в зависимости от длины текста, т.е. подчиняется типу ошибки, произошедшей во время исполнения программы.
Третий вид объектов позволяет программисту полностью управлять выделением и освобождением памяти. Такие объекты называют динамически размещаемыми или, для краткости, просто динамическими. Динамический объект “живет” в пуле свободной памяти, называемой хипом. Программист создает его с помощью оператора new, а уничтожает с помощью оператора delete. Динамически размещаться может как единичный объект, так и массив объектов. Размер массива, размещаемого в хипе, разрешается задавать во время выполнения.
В этом разделе, посвященном динамическим объектам, мы рассмотрим три формы оператора new: для размещения единичного объекта, для размещения массива и третью форму, называемую оператором размещения new (placement new expression). Когда хип исчерпан, этот оператор возбуждает исключение. (Разговор об исключениях будет продолжен в главе 11. В главе 15 мы расскажем об операторах new и delete применительно к классам.)
8.4.1. Динамическое создание и уничтожение единичных объектов
Оператор new состоит их ключевого слова new, за которым следует спецификатор типа. Этот спецификатор может относиться к встроенным типам или к типам классов. Например:
new int;
размещает в хипе один объект типа int. Аналогично в результате выполнения инструкции
new iStack;
там появится один объект класса iStack.
Сам по себе оператор new не слишком полезен. Как можно реально воспользоваться созданным объектом? Одним из аспектов работы с памятью из хипа является то, что размещаемые в ней объекты не имеют имени. Оператор new возвращает не сам объект, а указатель на него. Все манипуляции с этим объектом производятся косвенно через указатели:
int *pi = new int;
Здесь оператор new создает один объект типа int, на который ссылается указатель pi. Выделение памяти из хипа во время выполнения программы называется динамическим выделением. Мы говорим, что память, адресуемая указателем pi, выделена динамически.
Второй аспект, относящийся к использованию хипа, состоит в том, что эта память не инициализируется. Она содержит “мусор”, оставшийся после предыдущей работы. Проверка условия:
if ( *pi == 0 )
вероятно, даст false, поскольку объект, на который указывает pi, содержит случайную последовательность битов. Следовательно, объекты, создаваемые с помощью оператора new, рекомендуется инициализировать. Программист может инициализировать объект типа int из предыдущего примера следующим образом:
int *pi = new int( 0 );
Константа в скобках задает начальное значение для создаваемого объекта; теперь pi ссылается на объект типа int, имеющий значение 0. Выражение в скобках называется инициализатором. Это может быть любое выражение (не обязательно константа), возвращающее значение, приводимое к типу int.
Оператор new выполняет следующую последовательность действий: выделяет из хипа память для объекта, затем инициализирует его значением, стоящим в скобках. Для выделения памяти вызывается библиотечная функция new(). Предыдущий оператор приблизительно эквивалентен следующей последовательности инструкций:
int ival = 0; // создаем объект типа int и инициализируем его 0
int *pi = &ival; // указатель ссылается на этот объект
не считая, конечно, того, что объект, адресуемый pi, создается библиотечной функцией new() и размещается в хипе. Аналогично
iStack *ps = new iStack( 512 );
создает объект типа iStack на 512 элементов. В случае объекта класса значение или значения в скобках передаются соответствующему конструктору, который вызывается в случае успешного выделения памяти. (Динамическое создание объектов классов более подробно рассматривается в разделе 15.8. Оставшаяся часть данного раздела посвящена созданию объектов встроенных типов.)
Описанные операторы new могут вызывать одну проблему: хип, к сожалению, является конечным ресурсом, и в некоторой точке выполнения программы мы можем исчерпать его. Если функция new() не может выделить затребованного количества памяти, она возбуждает исключение bad_alloc. (Обработка исключений рассматривается в главе 11.)
Время жизни объекта, на который указывает pi, заканчивается при освобождении памяти, где этот объект размещен. Это происходит, когда pi передается оператору delete. Например,
delete pi;
освобождает память, на которую ссылается pi, завершая время жизни объекта типа int. Программист управляет окончанием жизни объекта, используя оператор delete в нужном месте программы. Этот оператор вызывает библиотечную функцию delete(), которая возвращает выделенную память в хип. Поскольку хип конечен, очень важно возвращать ее своевременно.
Глядя на предыдущий пример, вы можете спросить: а что случится, если значение pi по какой-либо причине было нулевым? Не следует ли переписать этот код таким образом:
// необходимо ли это?
if ( pi != 0 )
delete pi;
Нет. Язык С++ гарантирует, что оператор delete не будет вызывать функцию delete() в случае нулевого операнда. Следовательно, проверка на 0 необязательна. (Если вы явно добавите такую проверку, в большинстве реализаций она фактически будет выполнена дважды.)
Важно понимать разницу между временем жизни указателя pi и объекта, который он адресует. Сам объект pi является глобальным и объявлен в глобальной области видимости. Следовательно, память под него выделяется до выполнения программы и сохраняется за ним до ее завершения. Совсем не так определяется время жизни адресуемого указателем pi объекта, который создается с помощью оператора new во время выполнения. Область памяти, на которую указывает pi, выделена динамически, следовательно, pi является указателем на динамически размещенный объект типа int. Когда в программе встретится оператор delete, эта память будет освобождена. Однако память, отведенная самому указателю pi, не освобождается, а ее содержимое не изменяется. После выполнения delete объект pi становится висячим указателем, то есть ссылается на область памяти, не принадлежащую программе. Такой указатель служит источником трудно обнаруживаемых ошибок, поэтому сразу после уничтожения объекта ему полезно присвоить 0, обозначив таким образом, что указатель больше ни на что не ссылается.
Оператор delete может использоваться только по отношению к указателю, который содержит адрес области памяти, выделенной в результате выполнения оператора new. Попытка применить delete к указателю, не ссылающемуся на такую память, приведет к непредсказуемому поведению программы. Однако, как было сказано выше, этот оператор можно применять к нулевому указателю.
Ниже приведены примеры опасных и безопасных операторов delete:
void f() {
int i;
string str = "dwarves";
int *pi = &i;
short *ps = 0;
double *pd = new doub1e(33);
delete str; // плохо: str не является динамическим объектом
delete pi; // плохо: pi ссылается на локальный объект
delete ps; // безопасно
delete pd; // безопасно
}
Вот три основные ошибки, связанные с динамическим выделением памяти:
не освободить выделенную память. В таком случае память не возвращается в хип. Эта ошибка получила название утечки памяти;
дважды применить оператор delete к одной и той же области памяти. Такое бывает, когда два указателя получают адрес одного и того же динамически размещенного объекта. В результате подобной ошибки мы вполне можем удалить нужный объект. Действительно, память, освобожденная с помощью одного из адресующих ее указателей, возвращается в хип и затем выделяется под другой объект. Затем оператор delete применяется ко второму указателю, адресовавшему старый объект, а удаляется при этом новый;
изменять объект после его удаления. Такое часто случается, поскольку указатель, к которому применяется оператор delete, не обнуляется.
Эти ошибки при работе с динамически выделяемой памятью гораздо легче допустить, нежели обнаружить и исправить. Для того чтобы помочь программисту, стандартная библиотека С++ представляет класс auto_ptr. Мы рассмотрим его в следующем подразделе. После этого мы покажем, как динамически размещать и уничтожать массивы, используя вторую форму операторов new и delete.