Dyzzet|
C++ Data Science Алгоритмы Темы · Блог · YouTube · Telegram
Зачем нужны owner_before и owner_less?

Конструирование std::shared_ptr

Умный указатель std::shared_ptr имеет очень хитрую вещь — конструктор псевдонима (aliasing constructor).

template <class U>
shared_ptr(const shared_ptr<U>& x, element_type* ptr) noexcept; // C++11
 
template <class U>
shared_ptr(shared_ptr<U>&& x, element_type* ptr) noexcept;      // C++20

Указатель x — владеемый указатель (owned pointer). Ниже подробно рассмотрим его роль.

Указатель ptr — это хранимый указатель (stored pointer), он хранится, но std::shared_ptr им не владеет и не управляет его временем жизни (сюда нужно передать «сырой» указатель).

Есть некий класс, создадим разделяемый указатель на его экемпляр.

struct Object
{
    int one;
    int two;
};
std::shared_ptr<Object> object{ 1, 2 };

Создадим два разделяемых указателя на отдельные поля созданного объекта.

std::shared_ptr<int> subobjectOne{ object, &object->one };
std::shared_ptr<int> subobjectTwo{ object, &object->two };

Функция get() будет возвращать именно хранимый указатель, а оператор * будет возвращать его «разыменованную» версию.

std::cout << *subobjectOne << std::endl; // 1
std::cout << *subobjectTwo << std::endl; // 2

А функция use_count() вернёт количество копий разделяемого указателя (счётчик сильных ссылок в контрольном блоке). В этом примере оно достигает трёх (object, subobjectOne, subobjectTwo).

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

Сравнение

В C++20 оператор < у std::shared_ptr удалён, вместо него — более общий оператор <=>.

Оператор < просто сравнивает объекты, стоящие за хранимыми указателями (std::less<V>()(lhs.get(), rhs.get())).

В случае же функции owner_before() два разделяемых указателя будут считаться эквивалентными, если у них один и тот же владеемый указатель (или они оба пустые). Поскольку владеемый указатель у weakOne и weakTwo один и тот же, они считаются эквивалентными. Следовательно, обе операции сравнения в примере возвращают false. Если бы владеемые указатели были разными, один из вызовов вернул бы true.

subobjectOne < subobjectTwo
true
subobjectTwo < subobjectOne
false
subobjectOne.owner_before(subobjectTwo)
false
subobjectTwo.owner_before(subobjectOne)
false

std::weak_ptr

std::weak_ptr<int> weakOne{ subobjectOne };
std::weak_ptr<int> weakTwo{ subobjectTwo };

Сравнивать напрямую два слабых указателя с помощью оператора < нельзя, только с помощью owner_before().

weakOne < weakTwo
 
weakTwo < weakOne
 
weakOne.owner_before(weakTwo)
false
weakTwo.owner_before(weakOne)
false

В контрольном блоке счётчик сильных ссылок по-прежнему равен 3, счётчик слабых ссылок увеличивается до 2.

std::owner_less

Класс std::map имеет шаблонные параметры не только для ключей и значений, но и ещё для двух более тонких вещей: компаратора и аллокатора.

template <class Key,
          class T,
          class Compare = less<Key>,
          class Alloc = allocator<pair<const Key, T>>>
class map;

Остановимся на компараторе. Если ключом словаря является разделяемый указатель, то следующее объявление:

std::map<std::shared_ptr<int>, int> valueBasedMap;

аналогично объявлению со стандартным компаратором:

std::map<std::shared_ptr<int>, int, std::less<std::shared_ptr<int>>> valueBasedMap;
valueBasedMap[subobjectOne] = 10;
valueBasedMap[subobjectTwo] = 20;

Словарь valueBasedMap теперь содержит значения 10 и 20.

Пустая шаблонная структура std::owner_less имеет две специализации с тремя перегруженными операторами <. Первая специализация — для типа std::shared_ptr, вторая — для типа std::weak_ptr, обе с разными сочетаниями аргументов. Тело каждой перегрузки тривиально: return x.owner_before(y);.

std::map<std::shared_ptr<int>, int, std::owner_less<std::shared_ptr<int>>> ownerBasedMap;
ownerBasedMap[subobjectOne] = 10;
ownerBasedMap[subobjectTwo] = 20;

А словарь ownerBasedMap содержит только один элемент: 20. Ключи subobjectOne и subobjectTwo теперь считаются одним ключом, поскольку std::owner_less смотрит именно на владеемые указатели, а они здесь одинаковые.

* * *

Вообще говоря, оба указателя в конструкторе псевдонима не обязаны быть связанными. В примере ниже p может быть и умным указателем, и «сырым» — в любом случае псевдоним не будет управлять его временем жизни.

std::shared_ptr<int> shared{ new int{ 100 } };
 
std::unique_ptr<int> p{ new int{ 200 } };
std::shared_ptr<int> alias{ shared, p.get() };
 
std::cout << *shared << std::endl; // 100
std::cout << *alias  << std::endl; // 200

Всё сказанное о функции owner_before, структуре std::owner_less и её использовании в словаре верно и в этом случае.

Приложение: исходный код.

25 августа 2022
Зарегистрируйтесь и войдите, чтобы оставлять комментарии и голосовать.

Также может быть интересным
© MMXI—MMXXV. RSS
 Boosty
Светлая тема / тёмная тема