Что такое умный указатель (smart pointer) и когда его нужно использовать?
Что такое умный указатель?
Умный указатель (smart pointer) — это класс-обёртка над обычным указателем, который автоматически управляет временем жизни объекта, на который он указывает. Главная идея: когда умный указатель уничтожается, он автоматически вызывает delete для своего объекта. Это решает проблемы утечек памяти и висячих указателей.
В C++11 появились три основных типа умных указателей из библиотеки <memory>:
1. std::unique_ptr — единоличный владелец
unique_ptr — это указатель, который гарантирует, что только один умный указатель может владеть объектом в любой момент времени. Его нельзя скопировать, только переместить. Когда unique_ptr уничтожается (выходит из области видимости), объект удаляется автоматически.
#include <memory>
class MyClass {
public:
int data;
MyClass(int d) : data(d) {}
};
void example() {
// Создаём unique_ptr с помощью std::make_unique (C++14)
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>(42);
// Используем как обычный указатель
std::cout << ptr->data; // 42
// Не нужно вызывать delete — всё сделает деструктор unique_ptr
} // здесь ptr уничтожается, MyClass автоматически удаляется
// Перемещение владения
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(10);
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // теперь ptr2 владеет объектом
// ptr1 теперь nullptr
2. std::shared_ptr — разделяемое владение
shared_ptr использует подсчёт ссылок. Несколько shared_ptr могут указывать на один и тот же объект. Объект удаляется только тогда, когда последний shared_ptr на него уничтожается или переназначается.
#include <memory>
void example() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
{
std::shared_ptr<MyClass> ptr2 = ptr1; // счётчик ссылок = 2
// Оба указателя работают с одним объектом
} // ptr2 уничтожается, счётчик = 1, объект жив
// ptr1 всё ещё работает
} // ptr1 уничтожается, счётчик = 0, объект удаляется
3. std::weak_ptr — слабая ссылка
weak_ptr — это умный указатель, который не увеличивает счётчик ссылок shared_ptr. Он используется для разрыва циклических зависимостей. Чтобы получить доступ к объекту, нужно временно «захватить» его как shared_ptr через метод lock().
#include <memory>
void example() {
std::shared_ptr<MyClass> shared = std::make_shared<MyClass>(42);
std::weak_ptr<MyClass> weak = shared; // счётчик не увеличивается
if (auto temp = weak.lock()) { // получаем shared_ptr, если объект ещё жив
std::cout << temp->data;
} else {
std::cout << "Объект уже удалён";
}
}
Когда что использовать?
unique_ptr— по умолчанию для 90% случаев. Когда объект имеет одного владельца. Идеально для членов класса, локальных переменных, возврата из функций.shared_ptr— когда несколько частей кода должны совместно владеть объектом (например, графовые структуры, кеши).weak_ptr— когда нужно наблюдать за объектом без продления его жизни (например, для разрыва циклических ссылок вshared_ptr).- Обычные сырые указатели — только для невладеющих наблюдений (когда вы уверены, что объект живёт дольше, и не удаляете его).
Переписываем ваш пример
#include <memory>
class MyClass {
public:
int data;
MyClass(int d) : data(d) {}
};
void example() {
// Вместо new/delete — unique_ptr
std::unique_ptr<MyClass> obj = std::make_unique<MyClass>(42);
// Работаем с объектом
std::cout << obj->data;
// delete вызывать не нужно — автоматически при выходе из области видимости
// Даже если будет исключение — всё равно корректно удалится
}
Важно: Всегда используйте std::make_unique и std::make_shared вместо new — это безопаснее и эффективнее.
Дополнение: производительность и практические советы
Первый ответ отлично объяснил основы. Добавлю несколько важных нюансов из практики.
Производительность
unique_ptr— имеет нулевые накладные расходы по сравнению с сырым указателем. Компилятор оптимизирует его до обычного указателя.shared_ptr— дороже, так как хранит счётчик ссылок (обычно два слова памяти) и требует атомарных операций при копировании. Используйте только когда действительно нужно разделяемое владение.
Практические советы
- Не смешивайте умные и сырые указатели в одном владении. Если вы передаёте
unique_ptrв функцию, которая ожидает сырой указатель, убедитесь, что функция не удаляет объект. - Для массивов используйте специализации:
std::unique_ptr<int[]>илиstd::vector. - Избегайте
shared_ptrв циклах — это приведёт к утечке памяти. Используйтеweak_ptrдля разрыва цикла. - Возвращайте
unique_ptrиз фабрик — это ясно показывает, что владение передаётся вызывающему коду. - Для полиморфных объектов умные указатели работают отлично:
std::unique_ptr<Base>может хранитьDerived.
В современном C++ (C++11 и новее) старайтесь вообще не писать new и delete вручную. Используйте умные указатели и контейнеры STL — это сделает код безопаснее и понятнее.