Динамические объекты
В C++ можно использовать различные типы объектов, которые различаются по использованию памяти. Так, глобальные объекты создаются при запуске программы и освобождаются при ее завершении. Локальные автоматические объекты создаются в блоке кода и удаляются, когда этот блок кода завершает работу. Локальные статические объекты создаются перед их первым использованием и освобождаются при завершении программы.
Глобальные, а также статические локальные объекты помещаются в статической памяти, а локальные автоматические объекты размещаются в стеке. Объекты в статической памяти и стеке создаются и удаляются компилятором. Статическая память очищается при завершении программы, а объекты из стека существуют, пока выполняется блок, в котором они определены.
В дополнение к этим типам в C++ можно создавать динамические объекты. Продолжительность их жизни не зависит от того, где они созданы. Динамические объекты существуют, пока не будут удалены явным образом. Динамические объекты размещаются в динамической памяти (free store).
Для управления динамическими объектами применяются операторы new и delete.
Оператор new выделяет место в динамической памяти для объекта и возвращает указатель на этот объект.
Оператор delete получает указатель на динамический объект и удаляет его из памяти.
Выделение памяти
Создание динамического объекта:
1
int *ptr = new int;
Оператор new создает новый объект типа int в динамической памяти и возвращает указатель на него. Значение такого объекта неопределено.
Также можно инициализировать объект при создании:
1
2
3
4
5
int *p1 = new int(); // значение по умолчанию - 0
std::cout << "p1: " << *p1 << std::endl; // 0
int *p2 = new int(12);
std::cout << "p2: " << *p2 << std::endl; // 12
Освобождение памяти
Динамические объекты будут существовать пока не будут явным образом удалены. И после завершения использования динамических объектов следует освободить их память с помощью оператора delete:
1
2
3
int *p1 = new int(12);
std::cout << "p1: " << *p1 << std::endl; // 12
delete p1;
Особенно это надо учитывать, если динамический объект создается в одной части кода, а используется в другой. Например:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
int* createPtr(int value)
{
int *ptr = new int(value);
return ptr;
}
void usePtr()
{
int *p1 = createPtr(10);
std::cout << *p1 << std::endl; // 10
delete p1; // объект надо освободить
}
int main()
{
usePtr();
return 0;
}
В функции usePtr получаем из функции createPtr указатель на динамический объект. Однако после выполнения функции usePtr этот объект автоматически не удаляется из памяти (как это происходит в случае с локальными автоматическими объектами). Поэтому его надо явным образом удалить, использовав оператор delete.
Использование объекта по указателю после его удаления или повторное применение оператора delete к указателю могут привести к непредсказуемым результатам:
1
2
3
4
5
6
7
int *p1 = new int(12);
std::cout << *p1 << std::endl; // 12
delete p1;
// ошибочные сценарии
std::cout << *p1 << std::endl; // объект по указателю p1 уже удален!
delete p1; // объект по указателю p1 уже удален!
Поэтому следует удалять объект только один раз.
Также нередко имеет место ситуация, когда на один и тот же динамический объект указывают сразу несколько указателей. Если оператор delete применен к одному из указателей, то память объекта освобождается, и по второму указателю этот объект мы использовать уже не сможем. Если же после этого ко второму указателю применить оператор delete, то динамическая память может быть нарушена.
В то же время недопустимость указателей после применения к ним оператора delete не означает, что эти указатели мы в принципе не сможем использовать. Мы сможем их использовать, если присвоим им адрес другого объекта:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
int main()
{
int *p1 = new int(12);
int *p2 = p1;
delete p1; // адреса в p1 и p2 недопустимы
p1 = new int(11); // p1 указывает на новый объект
std::cout << *p1 << std::endl; // 11
delete p1;
return 0;
}
Здесь после удаления объекта, на который указывает p1, этому указателю передается адрес другого объекта в динамической памяти. Соответственно мы также можем использовать указатель p1. В то же время адрес в указателе p2 по прежнему будет недействительным.