
Умный указатель 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 содержит указатель на один и тот же управляющий блок.
Оператор < просто сравнивает объекты, стоящие за хранимыми указателями (std::less<V>()(lhs.get(), rhs.get())).
В случае же функции owner_before() два разделяемых указателя будут считаться эквивалентными, если у них один и тот же владеемый указатель (или они оба пустые). Поскольку владеемый указатель у weakOne и weakTwo один и тот же, они считаются эквивалентными. Следовательно, обе операции сравнения в примере возвращают false. Если бы владеемые указатели были разными, один из вызовов вернул бы true.
std::weak_ptr<int> weakOne{ subobjectOne };
std::weak_ptr<int> weakTwo{ subobjectTwo };
Сравнивать напрямую два слабых указателя с помощью оператора < нельзя, только с помощью owner_before().
В контрольном блоке счётчик сильных ссылок по-прежнему равен 3, счётчик слабых ссылок увеличивается до 2.
Класс 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::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 и её использовании в словаре верно и в этом случае.
Приложение: исходный код.