Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Поскольку объект может больше не существовать, нельзя использовать указатель 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.
12.2. Динамические массивы
Операторы new и delete резервируют объекты по одному. Некоторым приложениям нужен способ резервировать хранилище для многих объектов сразу. Например, векторы и строки хранят свои элементы в непрерывной памяти и должны резервировать несколько элементов сразу всякий раз, когда контейнеру нужно повторное резервирование (см. раздел 9.4).
Для этого язык и библиотека предоставляют два способа резервирования всего массива объектов. Язык определяет второй вид оператора new, резервирующего и инициализирующего массив объектов. Библиотека предоставляет шаблон класса allocator, позволяющий отделять резервирование от инициализации. По причинам, описанным в разделе 12.2.2, применение класса allocator обычно обеспечивает лучшую производительность и более гибкое управление памятью.