Умные указатели в С++

Умные указатели - это классы-обертки для "сырых" указателей (raw pointers), которые автоматически управляют жизненным циклом динамически выделенных объектов.

Основные преимущества:

  • Автоматическое освобождение памяти

  • Безопасность при исключениях

  • Явное выражение семантики владения

  • Защита от утечек памяти


std::unique_ptr

Основные характеристики:
  • Эксклюзивное владение ресурсом

  • Невозможность копирования

  • Поддержка перемещения (move semantics)

  • Минимальные накладные расходы

Примеры использования:
Базовое использование:
#include <memory> void simple_unique() { // Создание unique_ptr std::unique_ptr<int> ptr1(new int(42)); // Предпочтительный способ с C++14 auto ptr2 = std::make_unique<int>(42); // Доступ к данным *ptr2 = 100; std::cout << *ptr2 << std::endl; // Автоматическое освобождение при выходе из области видимости }

Пояснение: Память автоматически освобождается при выходе ptr2 из области видимости.

Передача владения:

void transfer_ownership() { auto ptr1 = std::make_unique<std::string>("Hello"); // Перемещение владения std::unique_ptr<std::string> ptr2 = std::move(ptr1); if (!ptr1) { std::cout << "ptr1 теперь пуст" << std::endl; } std::cout << *ptr2 << std::endl; // Hello }

Пояснение: std::move позволяет передать владение от ptr1 к ptr2.

Пользовательский делитер:

void file_example() { // Использование пользовательского делитера для FILE* auto file_deleter = [](FILE* f) { if (f) fclose(f); std::cout << "Файл закрыт" << std::endl; }; std::unique_ptr<FILE, decltype(file_deleter)> file( fopen("test.txt", "w"), file_deleter); if (file) { fprintf(file.get(), "Hello, world!"); } }

Пояснение: Можно указать свою функцию для освобождения ресурса.


std::shared_ptr

Основные характеристики:
  • Разделяемое владение ресурсом

  • Подсчет ссылок (reference counting)

  • Возможность копирования

  • Небольшие накладные расходы

Примеры использования:
Базовое использование:
void shared_example() { // Создание shared_ptr auto ptr1 = std::make_shared<std::vector<int>>(10, 42); // Копирование увеличивает счетчик ссылок auto ptr2 = ptr1; std::cout << "Count: " << ptr1.use_count() << std::endl; // 2 { auto ptr3 = ptr1; std::cout << "Count: " << ptr1.use_count() << std::endl; // 3 } // ptr3 уничтожается, счетчик уменьшается std::cout << "Count: " << ptr1.use_count() << std::endl; // 2 }

Пояснение: Счетчик ссылок автоматически управляет временем жизни объекта.

Циклические ссылки и weak_ptr:

struct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // weak_ptr для разрыва цикла ~Node() { std::cout << "Узел удален" << std::endl; } }; void cyclic_references() { auto node1 = std::make_shared<Node>(); auto node2 = std::make_shared<Node>(); node1->next = node2; node2->prev = node1; // Используем weak_ptr вместо shared_ptr // После выхода из области видимости узлы будут корректно удалены }

Пояснение: weak_ptr предотвращает утечки памяти при циклических ссылках.

Пользовательский делитер:

void shared_deleter_example() { auto deleter = [](int* p) { std::cout << "Удаление с пользовательским делитером\n"; delete p; }; std::shared_ptr<int> ptr(new int(42), deleter); }

std::weak_ptr

Основные характеристики:
  • Не владеет объектом

  • Используется вместе с shared_ptr

  • Позволяет проверить существование объекта

  • Разрывает циклические ссылки

Примеры использования:

Проверка существования объекта:

void weak_check_example() { std::weak_ptr<int> weak; { auto shared = std::make_shared<int>(42); weak = shared; if (auto locked = weak.lock()) { std::cout << "Объект существует: " << *locked << std::endl; } } // shared уничтожается здесь if (weak.expired()) { std::cout << "Объект больше не существует" << std::endl; } }

Пояснение: weak_ptr позволяет безопасно проверить, существует ли еще объект.

Кэширование с weak_ptr:

class Cache { std::unordered_map<int, std::weak_ptr<std::string>> cache; public: std::shared_ptr<std::string> get(int key) { auto it = cache.find(key); if (it != cache.end()) { if (auto sp = it->second.lock()) { return sp; // Объект еще в памяти } } // Создаем новый объект auto sp = std::make_shared<std::string>("Value " + std::to_string(key)); cache[key] = sp; return sp; } };

Пояснение: weak_ptr полезен для реализации кэшей, которые не продлевают время жизни объектов.


Сравнение умных указателей

Характеристика unique_ptr shared_ptr weak_ptr
Владение Эксклюзивное Разделяемое Нет владения
Копирование Запрещено Разрешено Разрешено
Перемещение Разрешено Разрешено Разрешено
Накладные расходы Минимальные Подсчет ссылок Подсчет ссылок
Основное применение Единственный владелец Множество владельцев Наблюдение, разрыв циклов

Практические рекомендации

  1. Правила использования:

    • По умолчанию используйте unique_ptr

    • Используйте shared_ptr только при явной необходимости разделяемого владения

    • Используйте weak_ptr для наблюдения и разрыва циклических ссылок

  2. Производительность:

    // Медленнее (два выделения памяти) std::shared_ptr<Widget> sp(new Widget()); // Быстрее (одно выделение памяти) auto sp = std::make_shared<Widget>();
  3. Опасные ситуации:

    // Опасность: сырой указатель может быть удален void process(Widget* w); auto sp = std::make_shared<Widget>(); process(sp.get()); // Лучше передавать shared_ptr по значению или const& void safe_process(std::shared_ptr<Widget> w);
  4. Наследование и полиморфизм:

    class Base { public: virtual ~Base() = default; }; class Derived : public Base {}; std::unique_ptr<Base> ptr = std::make_unique<Derived>();

Умные указатели - мощный инструмент современного C++, который:

  • Значительно снижает риск утечек памяти

  • Делает код более безопасным и выразительным

  • Позволяет явно выражать семантику владения

  • Интегрируется с другими возможностями языка (исключениями, STL и т.д.)

Правильное применение умных указателей - признак профессионального C++ разработчика.


Комментарии

Добавить комментарий

Чтобы оставить комменатрий необходимо Авторизоваться