12.1.6. Класс weak_ptr

Класс weak_ptr (табл. 12.5) представляет интеллектуальный указатель, который не контролирует продолжительность существования объекта, на который он указывает. Он только указывает на объект, который контролирует указатель shared_ptr. Привязка указателя weak_ptr к указателю shared_ptr не изменяет счетчик ссылок этого указателя shared_ptr. Как только последний указатель shared_ptr на этот объект будет удален, удаляется и сам объект. Этот объект будет удален, даже если останется указатель weak_ptr на него. Имя weak_ptr отражает концепцию "слабого" совместного использования объекта.

Создаваемый указатель weak_ptr инициализируется из указателя shared_ptr:

auto p = make_shared<int>(42);

weak_ptr<int> wp(p); // wp слабо связан с p; счетчик ссылок p неизменен

Здесь указатели wp и p указывают на тот же объект. Поскольку совместное использование слабо, создание указателя wp не изменяет счетчик ссылок указателя p; это делает возможным удаление объекта, на который указывает указатель wp.

Таблица 12.5. Функции указателя weak_ptr

weak_ptr<T> w Обнуляет указатель weak_ptr, способный указывать на объект типа T weak_ptr<T> w(sp) Указатель weak_ptr на тот же объект, что и указатель sp типа shared_ptr. Тип Т должен быть приводим к типу, на который указывает sp w = p Указатель p может иметь тип shared_ptr или weak_ptr. После присвоения w разделяет собственность с указателем p w.reset() Обнуляет указатель w w.use_count() Возвращает количество указателей shared_ptr, разделяющих собственность с указателем w w.expired() Возвращает значение true, когда функция w.use_count() должна возвратить нуль, и значение false в противном случае w.lock() Возвращает нулевой указатель shared_ptr, если функция expired() должна возвратить значение true; в противном случае возвращает указатель shared_ptr на объект, на который указывает указатель w

Поскольку объект может больше не существовать, нельзя использовать указатель weak_ptr для непосредственного доступа к его объекту. Для этого следует вызвать функцию lock(). Она проверяет существование объекта, на который указывает указатель weak_ptr. Если это так, то функция lock() возвращает указатель shared_ptr на совместно используемый объект. Такой указатель гарантирует существование объекта, на который он указывает, по крайней мере, пока существует этот указатель shared_ptr. Рассмотрим пример:

if (shared_ptr<int> np = wp.lock()) { // true, если np не нулевой

 // в if, np совместно использует свой объект с p

}

Внутренняя часть оператора if доступна только в случае истинности вызова функции lock(). В операторе if использование указателя np для доступа к объекту вполне безопасно.

Проверяемый класс указателя

Для того чтобы проиллюстрировать, насколько полезен указатель weak_ptr, определим вспомогательный класс указателя для нашего класса StrBlob. Класс указателя, назовем его StrBlobPtr, будет хранить указатель weak_ptr на переменную-член data класса StrBlob, которым он был инициализирован. Использование указателя weak_ptr не влияет на продолжительность существования вектора, на который указывает данный объект класса StrBlob. Но можно воспрепятствовать попытке доступа к вектору, которого больше не существует.

Класс StrBlobPtr будет иметь две переменные-члена: указатель wptr, который может быть либо нулевым, либо указателем на вектор в объекте класса StrBlob; и переменную curr, хранящую индекс элемента, который в настоящее время обозначает этот объект. Подобно вспомогательному классу класса StrBlob, у класса указателя есть функция-член check(), проверяющая безопасность обращения к значению StrBlobPtr:

// StrBlobPtr передает исключение при попытке доступа к

// несуществующему элементу

class StrBlobPtr {

 public:

 StrBlobPtr() : curr(0) { }

 StrBlobPtr(StrBlob &a, size_t sz = 0):

  wptr(a.data), curr(sz) { }

 std::string& deref() const;

 StrBlobPtr& incr(); // префиксная версия

private:

 // check() возвращает shared_ptr на вектор, если проверка успешна

 std::shared_ptr<std::vector<std::string>>

  check(std::size_t, const std::string&) const;

 // хранит weak_ptr, означая возможность удаления основного вектора

 std::weak_ptr<std::vector<std::string>> wptr;

 std::size_t curr; // текущая позиция в пределах массива

};

Стандартный конструктор создает нулевой указатель StrBlobPtr. Список инициализации его конструктора (см. раздел 7.1.4) явно инициализирует переменную-член curr нулем и неявно инициализирует указатель-член wptr как нулевой указатель weak_ptr. Второй конструктор получает ссылку на StrBlob и (необязательно) значение индекса. Этот конструктор инициализирует wptr как указатель на вектор данного объекта класса StrBlob и инициализирует переменную curr значением sz. Используем аргумент по умолчанию (см. раздел 6.5.1) для инициализации переменной curr, чтобы обозначить первый элемент. Как будет продемонстрировано, ниже параметр sz будет использован функцией-членом end() класса StrBlob.

Следует заметить, что нельзя связать указатель StrBlobPtr с константным объектом класса StrBlob. Это ограничение следует из того факта, что конструктор получает ссылку на неконстантный объект типа StrBlob.

Функция-член check() класса StrBlobPtr отличается от таковой у класса StrBlob, поскольку она должна проверять, существует ли еще вектор, на который он указывает:

std::shared_ptr<std::vector<std::string>>

StrBlobPtr::check(std::size_t i, const std::string &msg) const {

 auto ret = wptr.lock(); // существует ли еще вектор?

 if (!ret)

  throw std::runtime_error("unbound StrBlobPtr");

 if (i >= ret->size())

  throw std::out_of_range(msg);

 return ret; // в противном случае, возвратить shared_ptr на вектор

}

Так как указатель weak_ptr не влияет на счетчик ссылок соответствующего указателя shared_ptr, вектор, на который указывает StrBlobPtr, может быть удален. Если вектора нет, функция lock() возвратит нулевой указатель. В таком случае любое обращение к вектору потерпит неудачу и приведет к передаче исключения. В противном случае функция check() проверит переданный индекс. Если значение допустимо, функция check() возвратит указатель shared_ptr, полученный из функции lock().

Операции с указателями

Определение собственных операторов рассматривается в главе 14, а пока определим функции deref() и incr() для обращения к значению и инкремента указателя класса StrBlobPtr соответственно.

Функция-член deref() вызывает функцию check() для проверки безопасности использования вектора и принадлежности индекса curr его диапазону:

std::string& StrBlobPtr::deref() const {

 auto p = check(curr, "dereference past end");

 return (*p)[curr]; // (*p) - вектор, на который указывает этот объект

}

Если проверка прошла успешно, то p будет указателем типа shared_ptr на вектор, на который указывает данный указатель StrBlobPtr. Выражение (*p)[curr] обращается к значению данного указателя shared_ptr, чтобы получить вектор, и использует оператор индексирования для доступа и возвращения элемента по индексу curr.

Функция-член incr() также вызывает функцию check():

// префикс: возвратить ссылку на объект после инкремента

StrBlobPtr& StrBlobPtr::incr() {

 // если curr уже указывает на элемент после конца контейнера,

 // его инкремент не нужен

 check(curr, "increment past end of StrBlobPtr");

 ++curr; // инкремент текущего состояния

 return *this;

}

Безусловно, чтобы получить доступ к переменной-члену data, наш класс указателя должен быть дружественным классу StrBlob (см. раздел 7.3.4). Снабдим также класс StrBlob функциями begin() и end(), возвращающими указатель StrBlobPtr на себя:

// предварительное объявление необходимо для объявления дружественным

// классу StrBlob

class StrBlobPtr;

class StrBlob {

 friend class StrBlobPtr;

 // другие члены, как в разделе 12.1.1

 // возвратить указатель StrBlobPtr на первый и следующий

 // после последнего элементы

 StrBlobPtr begin() { return StrBlobPtr(*this); }

 StrBlobPtr end()

  { auto ret = StrBlobPtr(*this, data->size());

    return ret; }

};

Упражнения раздела 12.1.6

Упражнение 12.19. Определите собственную версию класса StrBlobPtr и модифицируйте класс StrBlob соответствующим объявлением дружественным, а также функциями-членами begin() и end().

Упражнение 12.20. Напишите программу, которая построчно читает исходный файл в операционной системе класса StrBlob и использует указатель StrBlobPtr для вывода каждого его элемента.

Упражнение 12.21. Функцию-член deref() класса StrBlobPtr можно написать следующим образом:

std::string& deref() const

{ return (*check(curr, "dereference past end"))[curr]; }

Какая версия по-вашему лучше и почему?

Упражнение 12.22. Какие изменения следует внести в класс StrBlobPtr, чтобы получить класс, применимый с типом const StrBlob? Определите класс по имени ConstStrBlobPtr, способный указывать на const StrBlob.

Больше книг — больше знаний!

Заберите 30% скидку новым пользователям на все книги Литрес с нашим промокодом

ПОЛУЧИТЬ СКИДКУ