Вопрос 51: Умные указатели: разделяемый и слабый указатели

Умный указатель (англ. smart pointer) — класс (обычно шаблонный), имитирующий интерфейс обычного указателя и добавляющий некую новую функциональность, например проверку границ при доступе или очистку памяти.

Чаще всего умный указатель инкапсулирует семантику владения ресурсом. В таком случае он называется владеющим указателем.

Владеющие указатели применяются для борьбы с утечками памяти и висячими ссылками. Утечкой памяти называется ситуация, когда в программе нет ни одного указателя, хранящего адрес объекта, созданного в динамической памяти. Висячей ссылкой называется указатель, ссылающийся на уже удалённый объект. Семантика владения для динамически созданных объектов означает, что удаление или присвоение нового значения указателю будет согласовано с временем жизни объекта.

В стандарте C++11 появились следующие умные указатели: unique_ptr, shared_ptr и weak_ptr. Все они объявлены в заголовочном файле <memory>.

Разделяемый указатель (shared_ptr)

Это самый популярный и самый широкоиспользуемый умный указатель. Он начал своё развитие как часть библиотеки boost. Данный указатель был столь успешным, что его включили в C++ Technical Report 1 и он был доступен в пространстве имен tr1 — std::tr1::shared_ptr<>. В отличии от рассмотренных выше указателей, shared_ptr реализует подсчет ссылок на ресурс. Ресурс освободится тогда, когда счетчик ссылок на него будет равен 0. Как видно, система реализует одно из основных правил сборщика мусора.

std::shared_ptr<int> x_ptr(new int(42));
std::shared_ptr<int> y_ptr(new int(13));

// после выполнения данной строчки, ресурс
// на который указывал ранее y_ptr (int(13)) освободится,
// а на int(42) будут ссылаться оба указателя
y_ptr = x_ptr;

std::cout << *x_ptr << "\t" << *y_ptr << std::endl;

// int(42) освободится лишь при уничтожении последнего ссылающегося
// на него указателя

Также как и unique_ptr, и auto_ptr, данный класс предоставляет методы get() и reset().

auto ptr = std::make_shared<Foo>();

Foo *foo = ptr.get();
foo->bar();

ptr.reset();

При работе с умным указателем, следует опасаться их создания на лету. Например, следующий код может привести к утечки памяти.

someFunction(std::shared_ptr<Foo>(new Foo), getRandomKey());

Почему? Да потому, что стандарт C++ не определяет порядок вычисления аргументов. Может случиться так, что сначала выполнится new Foo, затем getRandomKey() и лишь затем конструктор shared_ptr. Если же функция getRandomKey() бросит исключение, до конструктора shared_ptr дело не дойдет, хотя ресурс (объект Foo) был уже выделен. В случае с shared_ptr есть выход — использовать фабричную функцию std::make_shared<>, которая создает объект заданного типа и возвращает shared_ptr указывающий на него.

someFunction(std::make_shared<Foo>(), getRandomKey());

Почему и как это работает? Очень просто. Как я уже сказал выше, make_shared возвращает shared_ptr. Этот результат является временным объектом, а стандарт C++ четко декларирует, что временные объекты уничтожаются, в случае появления исключения.

Слабый указатель (weak_ptr)

Этот указатель также, как и shared_ptr начал свое рождение в проекте boost, затем был включен в C++ Technical Report 1 и, наконец, пришел в новый стандарт. Данный класс позволяет разрушить циклическую зависимость, которая, несомненно, может образоваться при использовании shared_ptr. Предположим, есть следующая ситуация (переменные-члены не инкапсулированы для упрощения кода)

class Bar;

class Foo
{
public:
    Foo() { std::cout << "Foo()" << std::endl; }
    ~Foo() { std::cout << "~Foo()" << std::endl; }

    std::shared_ptr<Bar> bar;
};


class Bar
{
public:
    Bar() { std::cout << "Bar()" << std::endl; }
    ~Bar() { std::cout << "~Bar()" << std::endl; }

    std::shared_ptr<Foo> foo;
};


int main()
{
    auto foo = std::make_shared<Foo>();

    foo->bar = std::make_shared<Bar>();
    foo->bar->foo = foo;

    return 0;
}

Как видно, объект foo ссылается на bar и наоборот. Образован цикл, из-за которого не вызовутся деструкторы объектов. Для того чтобы разорвать этот цикл, достаточно в классе Bar заменить shared_ptr на weak_ptr.

weak_ptr не позволяет работать с ресурсом напрямую, но зато обладает методом lock(), который генерирует shared_ptr().

std::shared_ptr<Foo> ptr = std::make_shared<Foo>();
std::weak_ptr<Foo> w(ptr);

if (std::shared_ptr<Foo> foo = w.lock())
{
    foo->doSomething();
}

results matching ""

    No results matching ""