Глава 5. Продвинутые концепции #
5.2 Управление памятью в C++ #
Управление памятью — критически важный аспект программирования на C++, требующий понимания механизмов выделения и освобождения ресурсов.
1. Динамическое выделение памяти #
1.1 Базовые принципы #
Динамическое выделение памяти позволяет создавать объекты во время выполнения программы с управляемым жизненным циклом.
// Простой пример динамического выделения памяти
int* dynamicInteger = new int(42); // Выделение памяти для одного целого числа
int* dynamicArray = new int[10]; // Выделение памяти для массива из 10 целых чисел
// Освобождение памяти
delete dynamicInteger; // Освобождение памяти для одиночного объекта
delete[] dynamicArray; // Освобождение памяти для массива
1.2 Важные замечания #
- Всегда освобождайте динамически выделенную память
- Невыполнение этого правила приводит к утечкам памяти
- Современный C++ рекомендует использовать умные указатели
2. Умные указатели #
2.1 Общие принципы #
Умные указатели автоматизируют управление памятью, предотвращая утечки и упрощая работу с динамическими объектами.
2.2 unique_ptr #
Эксклюзивный указатель, владеющий единственной ссылкой на объект.
#include <memory>
// Создание unique_ptr
std::unique_ptr<int> ptr1(new int(42));
// Передача владения
std::unique_ptr<int> ptr2 = std::move(ptr1); // ptr1 теперь nullptr
// Фабричный метод создания
auto ptr3 = std::make_unique<int>(100);
// Работа с массивами
auto arrayPtr = std::make_unique<int[]>(5);
Особенности unique_ptr: #
- Запрещено копирование
- Разрешено перемещение
- Автоматическое освобождение памяти при выходе из области видимости
- Минимальные накладные расходы
2.3 shared_ptr #
Указатель с подсчетом ссылок, позволяющий совместное владение объектом.
// Создание shared_ptr
auto sharedInt = std::make_shared<int>(42);
// Создание нескольких ссылок
auto sharedInt2 = sharedInt; // Увеличение счетчика ссылок
// Количество владельцев
std::cout << sharedInt.use_count() << std::endl; // 2
// Пользовательский deletor
auto customDeleter = [](int* p) {
std::cout << "Освобождение памяти" << std::endl;
delete p;
};
std::shared_ptr<int> ptr(new int(100), customDeleter);
Характеристики shared_ptr: #
- Атомарный подсчет ссылок
- Больше накладных расходов по сравнению с unique_ptr
- Автоматическое освобождение при последней ссылке
2.4 weak_ptr #
Слабая ссылка на объект, управляемый shared_ptr, не влияющая на подсчет ссылок.
// Пример использования weak_ptr
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// Проверка валидности
if (auto lockedPtr = weakPtr.lock()) {
std::cout << "Объект существует: " << *lockedPtr << std::endl;
} else {
std::cout << "Объект был удален" << std::endl;
}
Применение weak_ptr: #
- Предотвращение циклических зависимостей
- Слабые ссылки на кэшированные объекты
- Проверка существования объекта без увеличения счетчика ссылок
3. Операторы new и delete #
3.1 Низкоуровневое управление памятью #
// Базовое использование new и delete
class MyClass {
public:
MyClass() { std::cout << "Конструктор" << std::endl; }
~MyClass() { std::cout << "Деструктор" << std::endl; }
};
int main() {
// Выделение памяти
MyClass* obj1 = new MyClass(); // Вызов конструктора
MyClass* obj2 = new MyClass[5]; // Массив объектов
// Освобождение памяти
delete obj1; // Один объект
delete[] obj2; // Массив объектов
return 0;
}
3.2 Перегрузка new и delete #
class CustomMemoryManager {
public:
// Пользовательская реализация new
static void* operator new(size_t size) {
std::cout << "Пользовательский new" << std::endl;
return ::operator new(size);
}
// Пользовательская реализация delete
static void operator delete(void* ptr) noexcept {
std::cout << "Пользовательский delete" << std::endl;
::operator delete(ptr);
}
};
4. Утечки памяти #
4.1 Причины возникновения #
- Забытые
delete
- Потерянные указатели
- Неправильное использование динамической памяти
- Циклические зависимости в указателях
4.2 Пример утечки памяти #
void leakyFunction() {
int* leak = new int[1000]; // Память не освобождается
// Нет delete - утечка памяти
}
4.3 Методы предотвращения #
- Использование умных указателей
- RAII (Resource Acquisition Is Initialization)
- Статический анализ кода
- Инструменты профилирования памяти
Заключение #
Рекомендации #
- Предпочтительно использовать умные указатели
- Избегать ручного управления памятью
- Применять RAII
- Использовать средства статического анализа
Типичные ошибки #
- Двойное освобождение памяти
- Использование освобожденной памяти
- Забытые
delete
- Неправильное управление циклическими ссылками
Инструменты диагностики #
- Valgrind
- AddressSanitizer
- Visual Studio Memory Profiler
- CLion Memory Analysis
Дополнительные материалы #
- C++ Core Guidelines по управлению памятью
- Документация по std::unique_ptr, std::shared_ptr
- Современные техники безопасного управления ресурсами