← Назад к вопросам

В чем разница между обычным указателем и std::unique_ptr?

1.0 Junior🔥 161 комментариев
#Умные указатели и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

В чем разница между обычным указателем и std::unique_ptr?

Это один из критических вопросов современного C++. Разница принципиальна и касается управления памятью, безопасности и производительности.

Основная разница

Обычный указатель - это просто адрес в памяти, без какой-либо ответственности за освобождение:

int* ptr = new int(42);
// Вы несете ответственность за delete ptr
delete ptr;  // Если забудете - утечка памяти

std::unique_ptr - это smart pointer, который автоматически освобождает память при уничтожении:

std::unique_ptr<int> ptr(new int(42));
// или
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// Память автоматически освобождается, когда ptr выходит из scope

Ownership (Владение)

Самая важная концепция - уникальное владение. unique_ptr явно выражает, что:

  1. Ровно один объект владеет ресурсом
  2. При уничтожении владельца - ресурс освобождается
class DataProcessor {
private:
    std::unique_ptr<Database> database;  // Владеет БД
    
public:
    DataProcessor() 
        : database(std::make_unique<Database>()) {}
    
    // При уничтожении DataProcessor - Database автоматически удалится
    ~DataProcessor() = default;  // Даже пустой деструктор!
};

int main() {
    {
        DataProcessor processor;
        // processor.database жив
    }  // processor уничтожен -> database освобождена
    return 0;
}

Нет копирования - только перемещение

Критическое отличие: unique_ptr нельзя копировать, но можно перемещать:

std::unique_ptr<int> ptr1 = std::make_unique<int>(42);

// ОШИБКА КОМПИЛЯЦИИ:
// std::unique_ptr<int> ptr2 = ptr1;  // Копирование запрещено!

// ПРАВИЛЬНО: Перемещение
std::unique_ptr<int> ptr2 = std::move(ptr1);
// Теперь ptr2 владеет ресурсом, ptr1 пуст
ptr1 = nullptr;  // Это OK

Это гарантирует, что ровно один владелец в каждый момент времени.

Сравнение: Обычный указатель

int* ptr1 = new int(42);
int* ptr2 = ptr1;      // Два указателя на одно значение

delete ptr1;
// ptr2 теперь - "висячий" указатель (dangling pointer) - UB!

if (ptr2) {
    std::cout << *ptr2;  // Крах программы!
}

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

std::unique_ptr имеет нулевой overhead:

std::unique_ptr<int> ptr = std::make_unique<int>(42);
// Скомпилировано в то же самое, что и:
int* ptr = new int(42);

// unique_ptr - это просто обертка, компилятор оптимизирует до сырого указателя

Для пользовательского deleter'а:

// ПЛОХО: С пользовательским deleter'ом обычного типа
std::unique_ptr<FILE, decltype(&fclose)> file(fopen("file.txt", "r"), &fclose);
// Может иметь небольшой overhead из-за virtual call

// ХОРОШО: С lambda deleter'ом (оптимизируется лучше)
std::unique_ptr<FILE, std::function<void(FILE*)>> file(
    fopen("file.txt", "r"),
    [](FILE* f) { if (f) fclose(f); }
);

Использование в коллекциях

// Контейнер владеет объектами
std::vector<std::unique_ptr<Task>> tasks;

tasks.push_back(std::make_unique<Task>("task1"));
tasks.push_back(std::make_unique<Task>("task2"));

// При удалении из вектора - Task автоматически освобождается
tasks.erase(tasks.begin());

// При очистке вектора - все Task'и удаляются
tasks.clear();

// При уничтожении вектора - все Task'и удаляются

Пользовательский deleter

std::unique_ptr позволяет специфицировать, как освобождать ресурс:

// Пример 1: FILE с fclose
std::unique_ptr<FILE, decltype(&fclose)> file(
    fopen("data.txt", "r"),
    &fclose
);

// Пример 2: Custom allocator
struct CustomDeleter {
    void operator()(int* p) const {
        std::cout << "Освобождаю память\n";
        delete[] p;
    }
};

std::unique_ptr<int[], CustomDeleter> arr(new int[10]);

// Пример 3: Lambda
auto deleter = [](int* p) { 
    std::cout << "Удаляю значение: " << *p << "\n";
    delete p; 
};
std::unique_ptr<int, decltype(deleter)> ptr(new int(42), deleter);

Переход владения между функциями

std::unique_ptr отлично выражает передачу владения:

// Функция берет владение
void processData(std::unique_ptr<Data> data) {
    // data владеет объектом
    // При выходе из функции - объект удаляется
}

int main() {
    auto data = std::make_unique<Data>();
    processData(std::move(data));  // Передали владение
    
    // data пуст!
    if (!data) {
        std::cout << "Владение передано\n";
    }
}

Возврат из функции

// Функция создает и возвращает владение
std::unique_ptr<Database> createDatabase() {
    return std::make_unique<Database>("localhost");
}

int main() {
    std::unique_ptr<Database> db = createDatabase();
    // db владеет объектом Database
    // NRVO оптимизирует, нет копий!
    
    // db автоматически удалится
}

Сравнение: Утечки памяти

// С обычными указателями:
void processLegacy() {
    MyClass* obj = new MyClass();  // Выделили память
    
    if (someError()) {
        return;  // УТЕЧКА! obj не удален
    }
    
    doSomething(obj);
    delete obj;  // Может не вызваться
}

// С unique_ptr:
void processModern() {
    auto obj = std::make_unique<MyClass>();  // Выделили память
    
    if (someError()) {
        return;  // OK! obj автоматически удален
    }
    
    doSomething(obj.get());  // Получить сырый указатель
    
    // obj автоматически удален при выходе из scope
}

Как получить сырый указатель

Иногда нужно передать raw pointer в функцию, которая не берет владение:

std::unique_ptr<Object> obj = std::make_unique<Object>();

// Передать raw указатель (не передаем владение)
process(obj.get());

// Или с разименованием
obj->doSomething();

// Получить и отдать владение (dangerous!)
Object* raw = obj.release();  // obj становится nullptr
delete raw;  // Мы отвечаем за освобождение

Когда использовать unique_ptr

ВСЕГДА используйте unique_ptr вместо raw new/delete:

// ПЛОХО
MyClass* obj = new MyClass();
// ... работа ...
delete obj;  // Легко забыть

// ХОРОШО
auto obj = std::make_unique<MyClass>();
// Память автоматически освобождается

Иерархия преимуществ

  1. std::make_unique - лучший выбор (exception-safe)
  2. std::unique_ptr - защита от утечек
  3. std::shared_ptr - когда нужно общее владение
  4. raw pointers - ТОЛЬКО для non-owning ссылок
  5. new/delete - НИКОГДА в production коде

Итого

std::unique_ptr - это фундаментальный инструмент современного C++, который:

  • Гарантирует освобождение памяти
  • Предотвращает утечки памяти
  • Явно выражает владение
  • Не имеет runtime overhead
  • Делает code review проще
  • Облегчает многопоточность

Это не рекомендация, это требование при разработке production C++ кода.