В С++ 11 введены интеллектуальные указатели с подсчетом ссылок, std::shared_ptr
. При подсчете ссылок эти указатели не могут автоматически восстанавливать циклические структуры данных. Было показано, что автоматический сбор ссылочных циклов возможен, например, Python и PHP. Чтобы отличить этот метод от сбора мусора, остальная часть вопроса будет относиться к нему как к прерыванию цикла.
Учитывая, что, похоже, нет предложений по добавлению эквивалентной функциональности С++, существует ли фундаментальная причина, по которой автоматический выключатель, подобный тем, которые уже развернуты на других языках, не будет работать для std::shared_ptr
?
Обратите внимание, что этот вопрос не сводится к "почему нет GC для С++", который был задан до. С++ GC обычно относится к системе, которая автоматически управляет всеми динамически выделенными объектами, как правило, реализуется с использованием некоторой формы консервативного коллектива Бем. Было указано , что такой сборщик не подходит для RAII. Поскольку сборщик мусора в основном управляет памятью и даже не может быть вызван до тех пор, пока не будет нехватка памяти, а деструкторы С++ управляют другими ресурсами, опираясь на GC для запуска деструкторов, в лучшем случае будет вводиться не детерминированность и ресурсное голодание в худшем случае. Также было указано, что полномасштабный GC в значительной степени не нужен в присутствии более явных и предсказуемых интеллектуальных указателей.
Однако библиотечный прерыватель цикла для интеллектуальных указателей (аналогичный тому, который используется интерпретаторами с подсчетом ссылок) будет иметь важные отличия от GC общего назначения:
- Он заботится только об объектах, управляемых через
shared_ptr
. Такие объекты уже участвуют в совместном владении и, следовательно, должны обрабатывать задержанный вызов деструктора, чье точное время зависит от структуры собственности. - Из-за ограниченного объема, выключатель цикла не заботится о шаблонах, которые ломают или замедляют работу Boehm GC, таких как маскирование указателя или огромные непрозрачные блоки кучи, которые содержат случайный указатель.
- Он может быть включен, например
std::enable_shared_from_this
. Объекты, которые его не используют, не должны оплачивать дополнительное пространство в блоке управления для хранения метаданных цикла. - Выключатель цикла не требует исчерпывающего списка "корневых" объектов, который трудно получить на С++. В отличие от GC-метки развертки, которая находит все живые объекты и отбрасывает остальную часть, прерыватель цикла обходит только объекты, которые могут образовывать циклы. В существующих реализациях тип должен предоставлять помощь в виде функции, которая перечисляет ссылки (прямые или косвенные) другим объектам, которые могут участвовать в цикле.
- Он полагается на регулярное "уничтожение, когда количество ссылок уменьшается до нуля", чтобы уничтожить циклический мусор. Как только цикл идентифицируется, объектам, участвующим в нем, предлагается очистить свои сильно удерживаемые ссылки, например, вызывая
reset()
. Этого достаточно, чтобы разбить цикл и автоматически уничтожить объекты. Попросив объекты предоставить и очистить свои надежные ссылки (по запросу), убедитесь, что прерыватель цикла не разрушает инкапсуляцию.
Отсутствие предложений по автоматическому прерыванию цикла указывает на то, что идея была отвергнута по практическим или философским причинам. Мне любопытно, каковы причины. Для полноты, вот некоторые возможные возражения:
-
"Это приведет к детерминированному уничтожению циклических объектов
shared_ptr
". Если программист контролировал вызов цикла, он не был бы недетерминированным. Кроме того, после вызова поведение циклического выключателя было бы предсказуемым - оно уничтожило бы все известные в настоящее время циклы. Это сродни тому, как деструкторshared_ptr
разрушает базовый объект, когда его счетчик ссылок падает до нуля, несмотря на возможность этого вызвать "недетерминированный" каскад дальнейших разрушений. -
"Автоматический выключатель, как и любая другая форма сбора мусора, приведет к паузе при выполнении программы". Опыт с временем выполнения, который реализует эту функцию, показывает, что паузы минимальны, поскольку GC обрабатывает только циклический мусор, а все остальные объекты возвращаются путем подсчета ссылок. Если детектор цикла никогда не вызывается автоматически, прерывание цикла "пауза" может быть предсказуемым следствием его запуска, подобно тому, как уничтожение большого
std::vector
может запускать большое количество деструкторов. (В Python циклический gc запускается автоматически, но существует API для временно отключить его в разделах кода, где это не нужно. позволяя GC позже собирать все циклические мусора, созданные тем временем.) -
"Автоматический выключатель не нужен, потому что циклы не так часто, и их можно легко избежать, используя
std::weak_ptr
". Циклы фактически легко возникают во многих простых структурах данных - например, дерево, в котором дети имеют обратный указатель на родителя, или двусвязный список. В некоторых случаях циклы между гетерогенными объектами в сложных системах формируются лишь изредка с определенными образцами данных и их трудно предсказать и избежать. В некоторых случаях далеко не очевидно, какой указатель заменить на слабый вариант.