Проблемы ручного управления памятью
В C++ разработчики несут ответственность за управление памятью. Ручное использование new и delete приводит к утечкам, двойному освобождению и висячими указателям.
1// ❌ Проблемный код2void dangerous() {3 int* ptr = new int(42);4 // Если здесь произойдёт исключение, ptr не будет освобождён5 doSomething(ptr);6 delete ptr; // Может быть пропущен7}
Паттерн RAII
RAII (Resource Acquisition Is Initialization) — это идиома, при которой ресурсы привязываются к жизненному циклу объекта. При создании объекта ресурс приобретается, при уничтожении — освобождается.
▸Классический пример
1class FileHandler {2 FILE* file;3public:4 FileHandler(const char* filename) : file(fopen(filename, "r")) {5 if (!file) throw std::runtime_error("Cannot open file");6 }78 ~FileHandler() {9 if (file) fclose(file);10 }1112 // Запрещаем копирование13 FileHandler(const FileHandler&) = delete;14 FileHandler& operator=(const FileHandler&) = delete;15};
std::unique_ptr
Unique_ptr — это строгий владелец ресурса. Он обеспечивает единственность владения: только один unique_ptr может владеть объектом в данный момент.
▸Создание
1#include <memory>23// Создание unique_ptr4auto ptr1 = std::make_unique<int>(42);5auto ptr2 = std::make_unique<std::vector<int>>(std::initializer_list<int>{1, 2, 3});67// Передача владения (move semantics)8std::unique_ptr<int> ptr3 = std::move(ptr1); // ptr1 теперь nullptr
▸Использование
1class Resource {2public:3 void doWork() { std::cout << "Working\n"; }4};56void process() {7 auto resource = std::make_unique<Resource>();8 resource->doWork(); // Доступ через ->910 Resource* raw = resource.get(); // Получение raw-указателя (без потери владения)1112 // Автоматическое освобождение при выходе из scope13}
▸Unique_ptr и массивы
1auto arr = std::make_unique<int[]>(100);2arr[0] = 42;3arr[99] = 100;4// Автоматически вызывается delete[]
std::shared_ptr
Shared_ptr позволяет нескольким указателям владеть одним объектом. Объект освобождается только когда последний shared_ptr уничтожен.
▸Создание
1auto shared1 = std::make_shared<int>(42);2auto shared2 = shared1; // Счётчик ссылок = 23auto shared3 = shared2; // Счётчик ссылок = 345// Проверка счётчика ссылок6std::cout << "Use count: " << shared1.use_count() << "\n"; // 3
▸Shared_ptr и наследование
1class Base {2public:3 virtual ~Base() = default;4};56class Derived : public Base {7public:8 void specific() { std::cout << "Derived\n"; }9};1011std::shared_ptr<Base> ptr = std::make_shared<Derived>();12std::shared_ptr<Derived> derived = std::dynamic_pointer_cast<Derived>(ptr);
std::weak_ptr
Weak_ptr — это невладеющий указатель на объект, управляемый shared_ptr. Он не увеличивает счётчик ссылок.
▸Предотвращение циклических ссылок
1class Node {2public:3 std::shared_ptr<Node> next; // Сильная ссылка на следующий4 std::weak_ptr<Node> prev; // Слабая ссылка на предыдущий5};67// Без weak_ptr: circular reference → memory leak8// С weak_ptr: автоматическое освобождение
▸Проверка доступности
1std::weak_ptr<int> weak;23{4 auto shared = std::make_shared<int>(42);5 weak = shared;67 if (auto locked = weak.lock()) {8 std::cout << "Value: " << *locked << "\n"; // 429 }10}1112// Объект уничтожен13if (weak.expired()) {14 std::cout << "Object is gone\n";15}
Когда что использовать
Заключение
Smart pointers и RAII — это основа безопасной C++-разработки. Они предотвращают утечки памяти и делают код более предсказуемым. Используйте unique_ptr по умолчанию и прибегайте к shared_ptr только когда необходимо множественное владение.