Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Для освобождения динамического массива используется специальная форма оператора delete, имеющая пустую пару квадратных скобок:
delete p; // p должен указывать на динамически созданный объект или
// быть нулевым
delete [] pa; // pa должен указывать на динамически созданный
// объект или быть нулевым
Второй оператор удаляет элементы массива, на который указывает pa, и освобождает соответствующую память. Элементы массива удаляются в обратном порядке. Таким образом, последний элемент удаляется первым, затем предпоследний и т.д.
При применении оператора delete к указателю на массив пустая пара квадратных скобок необходима: она указывает компилятору, что указатель содержит адрес первого элемента массива объектов. Если пропустить скобки оператора delete для указателя на массив (или предоставить их, передав оператору delete указатель на объект), то его поведение будет непредсказуемо.
Напомним, что при использовании псевдонима типа, определяющего тип массива, можно зарезервировать массив без использования [] в операторе new. Но даже в этом случае нужно использовать скобки при удалении указателя на этот массив:
typedef int arrT[42]; // arrT имя типа массив из 42 целых чисел
int *p = new arrT; // резервирует массив из 42 целых чисел; p указывает
// на первый элемент
delete [] p; // скобки необходимы, поскольку был
// зарезервирован массив
Несмотря на внешний вид, указатель p указывает на первый элемент массива объектов, а не на отдельный объект типа arrT. Таким образом, при удалении указателя p следует использовать [].
Компилятор вряд ли предупредит нас, если забыть скобки при удалении указателя на массив или использовать их при удалении указателя на объект. Программа будет выполняться с ошибкой, не предупреждая о ее причине.
Интеллектуальные указатели и динамические массивыБиблиотека предоставляет версию указателя unique_ptr, способную контролировать массивы, зарезервированные оператором new. Чтобы использовать указатель unique_ptr для управления динамическим массивом, после типа объекта следует расположить пару пустых скобок:
// up указывает на массив из десяти неинициализированных целых чисел
unique_ptr<int[]> up(new int[10]);
up.release(); // автоматически использует оператор delete[] для
// удаления указателя
Скобки в спецификаторе типа (<int[]>) указывают, что указатель up указывает не на тип int, а на массив целых чисел. Поскольку указатель up указывает на массив, при удалении его указателя автоматически используется оператор delete[].
Указатели unique_ptr на массивы предоставляют несколько иные функции, чем те, которые использовались в разделе 12.1.5. Эти функции описаны в табл. 12.6. Когда указатель unique_ptr указывает на массив, нельзя использовать точечный и стрелочный операторы доступа к элементам. В конце концов, указатель unique_ptr указывает на массив, а не на объект, поэтому эти операторы были бы бессмысленны. С другой стороны, когда указатель unique_ptr указывает на массив, для доступа к его элементам можно использовать оператор индексирования:
for (size_t i = 0; i != 10; ++i)
up[i] = i; // присвоить новое значение каждому из элементов
Таблица 12.6. Функции указателя unique_ptr на массив
Операторы доступа к элементам (точка и стрелка) не поддерживаются указателями unique_ptr на массивы. Другие его функции неизменны unique_ptr<T[]> u u может указывать на динамически созданный массив типа T unique ptr<T[]> u(p) u указывает на динамически созданный массив, на который указывает встроенный указатель p. Тип указателя p должен допускать приведение к типу T (см. раздел 4.11.2). Выражение u[i] возвратит объект в позиции i массива, которым владеет указатель u. u должен быть указателем на массивВ отличие от указателя unique_ptr, указатель shared_ptr не оказывает прямой поддержки управлению динамическим массивом. Если необходимо использовать указатель shared_ptr для управления динамическим массивом, следует предоставить собственную функцию удаления:
// чтобы использовать указатель shared_ptr, нужно предоставить
// функцию удаления
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset(); // использует предоставленное лямбда-выражение, которое в
// свою очередь использует оператор delete[] для освобождения массива
Здесь лямбда-выражение (см. раздел 10.3.2), использующее оператор delete[], передается как функция удаления.
Если не предоставить функции удаления, результат выполнения этого кода непредсказуем. По умолчанию указатель shared_ptr использует оператор delete для удаления объекта, на который он указывает. Если объект является динамическим массивом, то при использовании оператора delete возникнут те же проблемы, что и при пропуске [], когда удаляется указатель на динамический массив (см. раздел 12.2.1).
Поскольку указатель shared_ptr не поддерживает прямой доступ к массиву, для обращения к его элементам применяется следующий код:
// shared_ptr не имеет оператора индексирования и не поддерживает
// арифметических действий с указателями
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // для доступа к встроенному указателю
// используется функция get()
Указатель shared_ptr не имеет оператора индексирования, а типы интеллектуальных указателей не поддерживают арифметических действий с указателями. В результате для доступа к элементам массива следует использовать функцию get(), возвращающую встроенный указатель, который можно затем использовать обычным способом.
Упражнения раздела 12.2.1Упражнение 12.23. Напишите программу, конкатенирующую два строковых литерала и помещающую результат в динамически созданный массив символов. Напишите программу, конкатенирующую две строки библиотечного типа string, имеющих те же значения, что и строковые литералы, используемые в первой программе.
Упражнение 12.24. Напишите программу, которая читает строку со стандартного устройства ввода в динамически созданный символьный массив. Объясните, как программа обеспечивает ввод данных переменного размера. Проверьте свою программу, введя строку, размер которой превышает длину зарезервированного массива.
Упражнение 12.25. С учетом следующего оператора new, как будет удаляться указатель pa?
int *pa = new int[10];
12.2.2. Класс allocator
Важный аспект, ограничивающий гибкость оператора new, заключается в том, что он объединяет резервирование памяти с созданием объекта (объектов) в этой памяти. Точно так же оператор delete объединяет удаление объекта с освобождением занимаемой им памяти. Обычно объединение инициализации с резервированием — это именно то, что и нужно при резервировании одиночного объекта. В этом случае почти наверняка известно значение, которое должен иметь объект.
Когда резервируется блок памяти, обычно в нем планируется создавать объекты по мере необходимости. В таком случае желательно было бы отделить резервирование памяти от создания объектов. Это позволит резервировать память в больших объемах, а дополнительные затраты на создание объектов нести только тогда, когда это фактически необходимо.
Зачастую объединение резервирования и создания оказывается расточительным. Например:
string *const p = new string[n]; // создает n пустых строк
string s;
string *q = p; // q указывает на первую строку
while (cin >> s && q != p + n)
*q++ = s; // присваивает новое значение *q
const size_t size = q - p; // запомнить количество прочитанных строк
// использовать массив
delete[] p; // p указывает на массив; не забыть использовать delete[]