Упражнение 13.25. Предположим, необходимо определить версию класса StrBlob, действующего как значение. Предположим также, что необходимо продолжить использовать указатель shared_ptr, чтобы класс StrBlobPtr все еще мог использовать указатель weak_ptr для вектора. Переделанный класс будет нуждаться в конструкторе копий и операторе присвоения копии, но не в деструкторе. Объясните, что должны делать конструктор копий и оператор присвоения копий. Объясните, почему класс не нуждается в деструкторе.

Упражнение 13.26. Напишите собственную версию класса StrBlob, описанного в предыдущем упражнении.

<p><image l:href="#reader.png"/>13.2.2. Определение классов, действующих как указатели</p>

Чтобы класс HasPtr действовал как указатель, конструктор копий и оператор присвоения копии должны копировать указатель-член, а не строку, на которую он указывает. Класс все еще будет нуждаться в собственном деструкторе, чтобы освободить память, зарезервированную получающим строку конструктором (см. раздел 13.6). Тем не менее в данном случае деструктор не может односторонне освободить связанную с ним строку. Это можно сделать только тогда, когда исчезнет последний указатель на строку.

Простейший способ заставить класс действовать как указатель — это использовать указатель shared_ptr для управления ресурсами в классе. При копировании (или присвоении) копируется (или присваивается) указатель shared_ptr. Класс shared_ptr сам отслеживает количество пользователей, совместно использующих объект, на который он указывает. Когда пользователей больше нет, класс shared_ptr освобождает ресурс.

Но иногда управлять ресурсом следует непосредственно. В таких случаях может пригодиться счетчик ссылок (reference count) (см. раздел 12.1.1). Для демонстрации работы счетчика ссылок переопределим класс HasPtr так, чтобы обеспечить поведение, подобное указателю, но с использованием собственного счетчика ссылок.

Счетчики ссылок

Счетчик ссылок работает следующим образом.

• В дополнение к инициализации объекта каждый конструктор (кроме конструктора копий) создает счетчик. Этот счетчик отслеживает количество объектов, совместно использующих создаваемые данные. Сразу после создания объект только один, поэтому счетчик инициализируется значением 1.

• Конструктор копий не создает новый счетчик; он копирует переменные-члены переданного ему объекта, включая счетчик. Конструктор копий увеличивает значение этого совместно используемого счетчика, указывая на наличие еще одного пользователя данных этого объекта.

• Деструктор уменьшает значение счетчика, указывая, что стало на одного пользователя совместно используемых данных меньше. Если значение счетчика достигает нуля, деструктор удаляет данные.

• Оператор присвоения копии увеличивает счетчик правого операнда и уменьшает счетчик левого. Если счетчик левого операнда достигает нуля, значит, пользователей больше нет. В данном случае оператор присвоения копии должен удалить данные левого операнда.

Единственное затруднение — это решить, где разместить счетчик ссылок. Счетчик не может быть членом непосредственно класса объекта HasPtr. Чтобы убедиться почему, рассмотрим происходящее в следующем примере:

HasPtr p1("Hiya!");

HasPtr p2(p1); // p1 и p2 указывают на ту же строку

HasPtr p3(p1); // p1, p2 и p3 указывают на ту же строку

Если счетчик ссылок будет храниться в каждом объекте, то как модифицировать его правильно при создании объекта p3? Можно увеличить счетчик в объекте p1 и скопировать счет в p3, но как модифицировать счетчик в p2?

Один из способов решения этой проблемы в том, чтобы хранить счетчик в динамической памяти. При создании объекта резервируется также и новый счетчик. При копировании или присвоении объекта копируется и указатель на счетчик. Таким образом, и копия, и оригинал укажут на тот же счетчик.

Определение класса счетчика ссылок

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

class HasPtr {

public:

 // конструктор резервирует новую строку и новый счетчик,

 // устанавливаемый в 1

 HasPtr(const std::string &s = std::string()):

  ps(new std::string(s)), i(0), use(new std::size_t(1)) {}

 // конструктор копий копирует все три переменные-члена и увеличивает

 // счетчик

 HasPtr(const HasPtr &p):

  ps(p.ps), i(p.i), use(p.use) { ++*use; }

 HasPtr& operator=(const HasPtr&);

 ~HasPtr();

private:

 std::string *ps;

 int i;

Перейти на страницу:

Похожие книги