C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Шрифт:
Интервал:
Закладка:
Поскольку существуют структуры данных, такие как unique_ptr и shared_ ptr, необходимость создавать объекты в куче вручную с помощью ключевых слов new и delete возникает редко. Используйте эти классы везде, где возможно! unique_ptr не создает никаких лишних издержек во время выполнения.
Автоматическое управление разделяемой памятью кучи с использованием std::shared_ptr
В предыдущем примере мы узнали, как использовать unique_ptr. Это очень полезный и важный класс, поскольку помогает нам управлять объектами, память для которых выделяется динамически. Однако он может владеть объектом только единолично. Нельзя сделать так, чтобы несколько объектов данного класса обладали одним динамически выделенным объектом, поскольку будет непонятно, кто именно должен удалить его.
Тип указателя shared_ptr был разработан специально для этого случая. Общие указатели могут быть скопированы любое количество раз. Внутренний механизм подсчета ссылок отслеживает, сколько объектов все еще содержат указатель на объект. Только последний общий указатель, выходящий за пределы области видимости, может удалить объект. Таким образом, можно быть уверенными в том, что утечек памяти не возникнет, поскольку объекты удаляются автоматически после использования, как и в том, что они не будут удаляться слишком рано или слишком часто (каждый созданный объект должен быть удален всего один раз).
В этом примере вы узнаете, как использовать shared_ptr для автоматического управления динамическими объектами, которые имеют несколько владельцев, и увидите, чем общие указатели отличаются от unique_ptr:.
Как это делается
В этом примере мы напишем программу, похожую на ту, которую мы писали в предыдущем примере, чтобы освоить основные принципы использования общих указателей.
1. Сначала включим необходимые заголовочные файлы и объявим об использовании пространства имен std:
#include <iostream>
#include <memory>
using namespace std;
2. Затем определим небольшой вспомогательный класс, который поможет увидеть, когда его экземпляры будут создаваться и разрушаться. Мы станем управлять его экземплярами с помощью shared_ptr:
class Foo
{
public:
string name;
Foo(string n)
: name{move(n)}
{ cout << "CTOR " << name << 'n'; }
~Foo() { cout << "DTOR " << name << 'n'; }
};
3. Далее реализуем функцию, которая принимает общий указатель на экземпляр типа Foo по значению. Передача общих указателей по значению в качестве аргументов более интересна, чем их передача по ссылке, поскольку в этом случае их нужно скопировать, что изменит их внутренний счетчик ссылок, как мы увидим далее.
void f(shared_ptr<Foo> sp)
{
cout << "f: use counter at "
<< sp.use_count() << 'n';
}
4. В функции main мы объявим пустой общий указатель. Вызвав его конструктор по умолчанию, мы сделаем его указателем null:
int main()
{
shared_ptr<Foo> fa;
5. Далее откроем еще одну область видимости и создадим два объекта типа Foo. Первый из них создадим с помощью оператора new и передадим его в конструктор типа shared_ptr. Далее создадим второй экземпляр, используя make_shared<Foo>, что позволяет создать экземпляр типа Foo на основе переданных нами параметров. Этот метод более элегантен, поскольку можно воспользоваться автоматическим выведением типов, и объект уже будет управляемым, когда у нас появится первая возможность получить к нему доступ. Это очень похоже на тот код, который мы писали в примере для unique_ptr.
{
cout << "Inner scope beginn";
shared_ptr<Foo> f1 {new Foo{"foo"}};
auto f2 (make_shared<Foo>("bar"));
6. Поскольку общие указатели могут быть разделяемыми, они должны отслеживать количество сторон, владеющих ими. Это делается с помощью внутреннего счетчика ссылок или счетчика использования. Можно вывести на экран его значение, задействуя use_count. Сейчас его значение равно 1, поскольку мы еще не копировали его. Копирование f1 в fa увеличит значение счетчика использования до 2.
cout << "f1's use counter at " << f1.use_count() << 'n';
fa = f1;
cout << "f1's use counter at " << f1.use_count() << 'n';
7. Когда мы покинем область видимости, общие указатели f1 и f2 будут уничтожены. Счетчик ссылок переменной f1 снова уменьшится до 1, что сделает fa единственным владельцем экземпляра типа Foo. При разрушении f2 его счетчик ссылок будет уменьшен до 0. В данном случае деструктор класса shared_ptr выполнит операцию delete для этого объекта, который удалит его.
}
cout << "Back to outer scopen";
cout << fa.use_count() << 'n';
8. Теперь вызовем функцию f для нашего общего указателя двумя разными способами. Сначала вызовем ее путем копирования fa. Функция f затем выведет на экран значение счетчика ссылок, которое равно 2. Во втором вызове f переместим указатель в функцию. Это сделает f единственным владельцем объекта.
cout << "first f() calln";
f(fa);
cout << "second f() calln";
f(move(fa));
9. После того как функция f отработает, экземпляр Foo будет мгновенно уничтожен, поскольку мы им больше не владеем. Поэтому все объекты подвергнутся уничтожению, когда отработает функция main.
cout << "end of main()n";
}
10. Компиляция и запуск программы дадут следующий результат. Сначала мы увидим, что созданы "foo" и "bar". После копирования f1 (указывает на "foo") его счетчик ссылок увеличился до значения 2. При выходе из области видимости "bar" уничтожается, поскольку общий указатель был его единственным владельцем. Одна единица на экране — счетчик ссылок fa, который является единственным владельцем "foo". После этого мы дважды вызываем функцию f. При первом