Smart-указатели. unique_ptr<T>

Последнее обновление: 25.02.2023

Smart pointers или "интеллектуальные указатели" — это объекты, которые имитируют стандартные указатели: они также содержат адрес (как правило, адрес выделенной динамической памяти), и их можно также использовать для обращения к объектам по этому адресу. Но главное их отличие от стандартных указателей состоит в том, что нам не надо беспокоиться об освобождении памяти с помощью операторов delete или delete[]. Вся выделенная память, используемая интеллектуальными указателями, будет освобождаться автоматически, когда она станет не нужна. Соответственно это означает, что мы не столкнемся с утечками памяти, не соответствием между выделениями и освобождениями памяти и болтающимися указателями. Таким образом, smart-указатели позволяют упростить и обезопасить управление памятью. Типы интеллектуальных указателей определены в модуле memory стандартной библиотеки языка С++ и доступны в пространстве имен std.

Указатель unique_ptr<T> представляет указатель на тип T, который является "уникальным" в том смысле, что что может быть только один объект unique_ptr, который содержит один и тот же адрес. То есть не может одновременно быть двух или более объектов unique_ptr<T>, которые указывают один и тот же адрес памяти. Если же мы попробуем определить два одновременно существующих указателя, которые указывают на один и тот же адрес, компилятор не скопирует код.

И когда unique_ptr уничтожается, уничтожается и значение, на которое он указывает. Соответственно данный тип указателей полезен, когда нужен указатель на объект, на который НЕ будет других указателей и который будет удален после удаления указателя.

По умолчанию unique_ptr<T> инициализируется значением nullptr

std::unique_ptr<int> ptr;	// ptr = nullptr
// аналогично
std::unique_ptr<int> ptr{};
std::unique_ptr<int> ptr{nullptr};

Чтобы выделить память и создать в ней объект, на который будет указывать указатель, применяется функция std::make_unique<T>. В качестве параметра в нее передается объект, на который будет указывать указатель:

std::unique_ptr<int> ptr { std::make_unique<int>(125) };

В данном случае выделяется динамическая память для хранения числа 125, и указатель ptr указывает на эту память.

Стоит отметить, что до принятия стандарта C++14 применялась другая форма создания указателя:

std::unique_ptr<int> ptr { new int(125) };

Для получения стандартного указателя из std::unique_ptr применяется функция get():

std::unique_ptr<int> ptr { std::make_unique<int>(125) };
int* pointer = ptr.get();

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

#include <iostream>
#include <memory>

int main()
{
    // указатель ptr указывает на объект 125
    std::unique_ptr<int> ptr { std::make_unique<int>(125) };
    std::cout << "Address: " << ptr.get() << std::endl;   // получим адрес объекта
    std::cout << "Initial value: " << *ptr << std::endl;     // получим значение объекта

    // изменяем значение
    *ptr = 254;
    std::cout << "New value: " << *ptr << std::endl;     // получим значение объекта
}

Консольный вывод:

Address: 0x2775dfa8030
Initial value: 125
New value: 254

Стоит отметить, что начиная со стандарта C++ 20 получить адрес из smart-указателя мы можем напрямую без функции get:

std::cout << "Address: " << ptr << std::endl;   // получим адрес объекта

unique_ptr также может работать с массивами. Например, определим указатель, который ссылается на массив:

unsigned n{5};   // размер массива
auto pnumbers { std::make_unique<int[]>(n) };    // массив из n элементов, равных 0

В данном случае указать pnumbers указаывает на массив из 5 элементов. При этом все эти элементы уже инициализированы значениями по умолчанию (для примитивных типов - числом 0).

С помощью квадратных скобок можно обращаться к определенным элементам массива:

#include <iostream>
#include <memory>

int main()
{
    unsigned n{5};   // размер массива
    auto pnumbers { std::make_unique<int[]>(n) };    // массив {0, 0, 0, 0, 0}

    std::cout << "pnumbers[1] initial value: " << pnumbers[1] << std::endl; // pnumbers[1] initial value: 0
    pnumbers[1] = 121;  // изменяем значение
    std::cout << "pnumbers[1] new value: " << pnumbers[1] << std::endl;     // pnumbers[1] new value: 121
}

Можно пройтись по массиву в цикле

#include <iostream>
#include <memory>

int main()
{
    unsigned n{5};   // размер массива
    auto pnumbers { std::make_unique<int[]>(n) };    // массив из n элементов

    // изменим и выведем все элементы на консоль
    for (unsigned i {}; i < n; i++)
    {
        pnumbers[i] = i+1;
        std::cout << "pnumbers[" << i <<"] = " << pnumbers[i] << std::endl;
    }
}

Консольный вывод:

pnumbers[0] = 1
pnumbers[1] = 2
pnumbers[2] = 3
pnumbers[3] = 4
pnumbers[4] = 5

Если потребуется освободить память, на которую указывает указатель, то можно применить функцию reset():

#include <iostream>
#include <memory>

int main()
{
    auto ptr { std::make_unique<int>(123) };

    // освобождаем память и удаляем объект 123
    ptr.reset();
    if(!ptr)   // если ptr
    {
        std::cout << "Memory is free" << std::endl;
    }
    else    
    {
        std::cout << *ptr << std::endl;
    }
}

После выполнения функции reset() указатель получает значение nullptr

Также можно передать в функцию reset() новый объект, для которого будет выделяться память и на который будет указывать указатель.

#include <iostream>
#include <memory>

int main()
{
    auto ptr { std::make_unique<int>(123) };

    std::cout << "Old address: " << ptr.get() << std::endl;
    std::cout << "Old value: " << *ptr << std::endl;
    // освобождаем память и удаляем массив
    ptr.reset(new int{254});
    
    std::cout << "New address: " << ptr.get() << std::endl;
    std::cout << "New value: " << *ptr << std::endl;
}

В данном случае после вызова функции reset() изначальная память освобождается и выделяется новый участок памяти для числа 254. Соотвественно меняется адрес и само значение, на которое указывает указатель. Консольный вывод:

Old address: 0x2b13dd73270
Old value: 123
New address: 0x2b13dd68010
New value: 254
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850