Полагаю, вы обратили внимание, что reclaim_later() — шаблон функции, а не обычная функция (4). Объясняется это тем, что указатели опасности — это универсальный механизм, поэтому не стоит ограничивать его только узлами стека. Ранее для хранения указателей мы использовали тип std::atomic. Поэтому мы должны обрабатывать произвольный указательный тип, но просто указать void* нельзя, так как мы собираемся удалять данные по указателю, а оператору delete нужно знать реальный тип указателя. Как мы скоро увидим, конструктор data_to_reclaim прекрасно справляется с этой проблемой: reclaim_later() просто создает новый экземпляр data_to_reclaim для переданного указателя и добавляет его в список отложенного освобождения (5). Сама функция add_to_reclaim_list() (3) — не более чем простой цикл по compare_exchange_weak() для головного элемента списка; мы уже встречались с такой конструкцией раньше.
Но вернёмся к конструктору data_to_reclaim (1), который также является шаблоном. Он сохраняет подлежащие удалению данные в виде указателя void* в члене data, после чего запоминает указатель на подходящую конкретизацию do_delete() — простую функцию, которая приводит тип void* к типу параметризованного указателя, а затем удаляет объект, на который он указывает. Шаблон std::function<> безопасно обертывает этот указатель на функцию, так что впоследствии деструктору data_to_reclaim для удаления данных нужно всего лишь вызвать запомненную функцию (2).
Деструктор data_to_reclaim не вызывается, когда мы добавляем узлы в список; он вызывается только, когда на узел не ссылается ни один указатель опасности. За это отвечает функция delete_nodes_with_no_hazards().
Эта функция сначала заявляет права на владение всем списком подлежащих освобождению узлов, вызывая exchange() (6). Это простое, но крайне важное действие гарантирует, что данный конкретный набор узлов будет освобождать только один поток. Другие потоки вправе добавлять в список новые узлы и даже пытаться освободить их, никак не затрагивая работу этого потока.
Далее мы по очереди просматриваем все узлы списка, проверяя, ссылаются ли на них не сброшенные указатели опасности (7). Если нет, то запись можно удалить (очистив хранящиеся в ней данные) (8). В противном случае мы возвращаем элемент в список, чтобы освободить его позже (9).
Хотя эта простая реализация справляется с задачей безопасного освобождения удаленных узлов, она заметно увеличивает накладные расходы. Для просмотра массива указателей опасности требуется проверить max_hazard_pointers атомарных переменных, и это делается при каждом вызове pop(). Атомарные операции по необходимости работают медленно — зачастую в 100 раз медленнее эквивалентных обычных операций на настольном ПК, — поэтому pop() оказывается дорогостоящей операцией. Мало того что приходится просматривать список указателей опасности для исключаемого из списка узла, так еще надо просмотреть его для каждого узла в списке ожидающих освобождения. Понятно, что это не слишком удачная идея. В списке может храниться max_hazard_pointers узлов, и каждый из них нужно сравнить с max_hazard_pointers хранимых указателей опасности. Черт! Должно существовать решение получше.