Умные указатели в C++
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 15 из 104.
Умные указатели — это классы-обертки, которые автоматически управляют памятью в куче за вас. Они устраняют необходимость в ручных вызовах delete, очищая память, когда указатель выходит из области видимости.
Подключите заголовочный файл <memory> для использования умных указателей.
unique_ptr — эксклюзивное владение. Только один указатель может владеть ресурсом в данный момент времени.
#include <memory>
std::unique_ptr<Player> player = std::make_unique<Player>("Hero");
player->attack(); // Используется как обычный указатель
// Нельзя копировать, но можно передать владение
std::unique_ptr<Player> player2 = std::move(player);
// player теперь равен nullptrshared_ptr - совместное владение. Несколько указателей могут владеть одним и тем же ресурсом. Память освобождается, когда уничтожается последний владелец.
std::shared_ptr<Player> p1 = std::make_shared<Player>("Hero");
std::shared_ptr<Player> p2 = p1; // Оба владеют ресурсом
std::cout << p1.use_count(); // Выводит 2weak_ptr — невладеющий наблюдатель для shared_ptr. Полезен для разрыва циклических ссылок.
std::shared_ptr<Player> shared = std::make_shared<Player>("Hero");
std::weak_ptr<Player> weak = shared;
if (auto locked = weak.lock()) { // Преобразование в shared_ptr
locked->attack();
}Используйте unique_ptr по умолчанию для единоличного владения. Используйте shared_ptr, когда нескольким объектам необходимо совместно использовать ресурс. Используйте weak_ptr для наблюдения без продления времени жизни.
Предпочитайте make_unique и make_shared вместо обычного new.
Задание
ЛегкоДавайте создадим систему обмена документами, которая демонстрирует различные модели владения умными указателями. Вы увидите, как unique_ptr обеспечивает исключительное владение, как shared_ptr позволяет иметь несколько владельцев и как weak_ptr может наблюдать за объектом, не влияя на время его жизни.
Вы создадите два файла для организации вашего кода:
Document.h: Определите классDocument, который представляет документ для совместного использования. Он должен иметь:- Приватный атрибут
title(строка) - Конструктор, который принимает заголовок и выводит
"Document created: <title>" - Деструктор, который выводит
"Document deleted: <title>" - Метод
getTitle(), который возвращает заголовок
- Приватный атрибут
main.cpp: Продемонстрируйте все три типа умных указателей, прочитав заголовок документа из входных данных, а затем:- Создайте
unique_ptrдля Document с введенным заголовком - Выведите
"Unique owner: <title>" - Передайте владение другому
unique_ptrс помощьюstd::move - Выведите
"Ownership transferred" - Создайте
shared_ptrдля нового Document с заголовком"SharedDoc" - Создайте второй
shared_ptr, указывающий на тот же документ - Выведите
"Shared owners: <count>", используяuse_count() - Создайте
weak_ptrиз одного из shared-указателей - Используйте
lock()для доступа к документу через weak-указатель и выведите"Weak access: <title>"
- Создайте
Не забудьте включить <memory> для работы с умными указателями. Используйте std::make_unique и std::make_shared для создания ваших умных указателей. Сообщения деструктора помогут вам увидеть, когда каждый документ автоматически удаляется в конце программы.
Включите ваш заголовочный файл в main.cpp с помощью #include "Document.h".
Шпаргалка
Умные указатели — это классы-обертки, которые автоматически управляют памятью в куче. Для их использования подключите заголовочный файл <memory>.
unique_ptr
Эксклюзивное владение — только один указатель может владеть ресурсом в данный момент времени:
std::unique_ptr<Player> player = std::make_unique<Player>("Hero");
player->attack(); // Используйте как обычный указатель
// Передача владения с помощью std::move
std::unique_ptr<Player> player2 = std::move(player);
// player теперь равен nullptrshared_ptr
Разделяемое владение — несколько указателей могут владеть одним и тем же ресурсом. Память освобождается, когда уничтожается последний владелец:
std::shared_ptr<Player> p1 = std::make_shared<Player>("Hero");
std::shared_ptr<Player> p2 = p1; // Оба владеют ресурсом
std::cout << p1.use_count(); // Выводит 2weak_ptr
Не владеющий наблюдатель для shared_ptr. Полезен для разрыва циклических ссылок:
std::shared_ptr<Player> shared = std::make_shared<Player>("Hero");
std::weak_ptr<Player> weak = shared;
if (auto locked = weak.lock()) { // Преобразование в shared_ptr
locked->attack();
}Лучшие практики: Используйте unique_ptr по умолчанию для единоличного владения. Используйте shared_ptr, когда нескольким объектам необходимо совместно использовать ресурс. Используйте weak_ptr для наблюдения без продления времени жизни. Отдавайте предпочтение make_unique и make_shared вместо обычного new.
Попробуйте сами
#include <iostream>
#include <memory>
#include <string>
#include "Document.h"
using namespace std;
int main() {
string inputTitle;
cin >> inputTitle;
// TODO: Создайте unique_ptr для Document с inputTitle
// используя std::make_unique
// TODO: Выведите "Unique owner: <title>"
// TODO: Передайте владение другому unique_ptr, используя std::move
// TODO: Выведите "Ownership transferred"
// TODO: Создайте shared_ptr для нового Document с заголовком "SharedDoc"
// используя std::make_shared
// TODO: Создайте второй shared_ptr, указывающий на тот же документ
// TODO: Выведите "Shared owners: <count>", используя use_count()
// TODO: Создайте weak_ptr из одного из shared-указателей
// TODO: Используйте lock() для доступа к документу через weak_ptr
// и выведите "Weak access: <title>"
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер