Почему слабые указатели полезны?
Я читал о сборке мусора в поисках возможностей для включения в мой язык программирования, и я наткнулся на "слабые указатели". Отсюда:
Слабые указатели похожи на указатели, за исключением того, что ссылки со слабых указателей не препятствуют сбору мусора, и перед их использованием слабые указатели должны проверить их правильность.
Слабые указатели взаимодействуют с сборщиком мусора, поскольку память, на которую они ссылаются, может фактически быть действительной, но содержать объект, отличный от того, который был при создании слабого указателя. Таким образом, всякий раз, когда сборщик мусора перезагружает память, он должен проверить, есть ли какие-либо слабые указатели, ссылающиеся на него, и пометить их как недействительные (это не должно быть реализовано таким наивным способом).
Я никогда не слышал о слабых указателей раньше. Я хотел бы поддержать многие функции на моем языке, но в этом случае я не могу на всю жизнь подумать о случае, когда это было бы полезно. Для чего можно использовать слабый указатель?
9 ответов
Типичный вариант использования - хранение дополнительных атрибутов объекта. Предположим, у вас есть класс с фиксированным набором членов, и извне вы хотите добавить больше членов. Таким образом, вы создаете объект словаря -> атрибуты, где ключи являются слабыми ссылками. Кроме того, словарь не предотвращает сбор мусора ключей; Удаление объекта также должно инициировать удаление значений в WeakKeyDictionary (например, с помощью обратного вызова).
Действительно большой кеширование. Давайте подумаем, как будет работать кеш:
Идея, стоящая за кешем, заключается в том, чтобы хранить объекты в памяти до тех пор, пока нагрузка на память не станет настолько сильной, что некоторые объекты нужно будет вытолкнуть (или, разумеется, явно объявить недействительными). Таким образом, ваш объект хранилища кэша должен каким-то образом удерживать эти объекты. Держась за них по слабой ссылке, когда сборщик мусора ищет вещи, которые потребляют из-за нехватки памяти, элементы, на которые ссылается только слабая ссылка, будут отображаться как кандидаты на сборку мусора. Элементы в кеше, которые в данный момент используются другим кодом, будут иметь жесткие ссылки, все еще активные, поэтому эти элементы будут защищены от сборки мусора.
В большинстве случаев вы не будете использовать свой собственный механизм кэширования, но обычно используется кеш. Предположим, вы хотите иметь свойство, которое ссылается на объект в кэше, и это свойство остается в области действия в течение длительного времени. Вы бы предпочли получить объект из кэша, но если он недоступен, вы можете получить его из постоянного хранилища. Вы также не хотите, чтобы этот конкретный объект оставался в памяти, если давление становится слишком высоким. Таким образом, вы можете использовать слабую ссылку на этот объект, что позволит вам извлечь его, если он доступен, но также позволит ему выпасть из кэша.
Если сборщик мусора на вашем языке не способен собирать циклические структуры данных, вы можете использовать слабые ссылки, чтобы сделать это. Обычно, если у вас есть два объекта, которые имеют ссылки друг на друга, но ни у одного внешнего объекта нет ссылки на эти два, они будут кандидатами на сборку мусора. Но наивный сборщик мусора не будет собирать их, поскольку они содержат ссылки друг на друга.
Чтобы это исправить, вы делаете так, чтобы один объект имел сильную ссылку на второй, а второй - на первый. Затем, когда последняя внешняя ссылка на первый объект исчезает, первый объект становится кандидатом на сборку мусора, за которым вскоре следует второй, поскольку теперь его единственная ссылка является слабой.
Другой пример... не совсем кеширование, но похожее: предположим, библиотека ввода-вывода предоставляет объект, который оборачивает дескриптор файла и разрешает доступ к файлу. Когда объект собран, дескриптор файла закрывается. Желательно иметь возможность перечислять все открытые в настоящее время файлы. Если вы используете сильные указатели для этого списка, то файлы никогда не закрываются.
Используйте их, когда вы хотите сохранить кэшированный список объектов, но не препятствовать тому, чтобы эти объекты собирали мусор, если с ним покончил "реальный" владелец объекта.
Веб-браузер может иметь объект истории, который хранит ссылки на объекты изображений, которые браузер загружал в другом месте и сохранял в кэше истории / диска. В веб-браузере может истечь срок действия одного из этих изображений (пользователь очистил кеш, истекло время ожидания кеша и т. Д.), Но страница все равно будет иметь ссылку / указатель. Если бы на странице использовалась слабая ссылка / указатель, объект удалялся бы, как и ожидалось, и память собиралась мусором.
Одной из важных причин наличия слабых ссылок является возможность того, что объект может служить конвейером для соединения источника информации или событий с одним или несколькими слушателями. Если нет никаких слушателей, нет никакой причины продолжать посылать информацию в конвейер.
Рассмотрим, например, перечислимую коллекцию, которая допускает обновления во время перечисления. Коллекция может потребовать уведомления любых активных перечислителей о том, что она была изменена, поэтому эти перечислители могут настроить себя соответствующим образом. Если некоторые перечислители будут заброшены их создателями, но коллекция содержит надежные ссылки на них, эти перечислители будут продолжать существовать (и обрабатывать уведомления об обновлениях), пока существует коллекция. Если сама коллекция будет существовать в течение всего жизненного цикла приложения, эти перечислители фактически станут постоянной утечкой памяти.
Если коллекция содержит слабые ссылки на перечислители, эта проблема может быть в значительной степени решена. Если перечислитель будет оставлен, он будет иметь право на сборку мусора, даже если коллекция все еще содержит слабую ссылку на него. В следующий раз, когда коллекция будет изменена, она может просмотреть свой список слабых ссылок, отправить обновления на те, которые все еще действительны, и удалить из своего списка те, которые не являются действительными.
Можно было бы добиться многих эффектов слабых ссылок, используя финализаторы вместе с некоторыми дополнительными объектами, и можно сделать такие реализации более эффективными, чем те, которые используют слабые ссылки, но есть много подводных камней и трудно избежать ошибок. Гораздо проще сделать правильный подход с помощью WeakReference. Подход не может быть оптимально эффективным, но он не потерпит неудачу.
Слабые указатели удерживают то, что удерживает их от превращения в форму "поддержки жизни" для объекта, на который указывает указатель.
Скажем, у вас был класс Viewport, 2 класса пользовательского интерфейса и несколько классов Widget. Вы хотите, чтобы ваш пользовательский интерфейс контролировал продолжительность жизни создаваемых им виджетов, поэтому ваш пользовательский интерфейс сохраняет SharedPtrs для всех виджетов, которыми он управляет. Пока ваш объект пользовательского интерфейса жив, ни один из виджетов, на которые он ссылается, не будет собирать мусор (благодаря SharedPtr).
Однако Viewport - это ваш класс, который на самом деле выполняет рисование, поэтому ваш пользовательский интерфейс должен передать Viewport указатель на Widgets, чтобы он мог их рисовать. По какой-то причине вы хотите изменить свой активный класс пользовательского интерфейса на другой. Давайте рассмотрим два сценария: один, где пользовательский интерфейс передал Viewport WeakPtrs, и другой, где он передал SharedPtrs (указывая на виджеты).
Если вы передали Viewport все виджеты как WeakPointers, как только класс пользовательского интерфейса будет удален, SharedPointers больше не будет виджетами, поэтому они будут собирать мусор, ссылки Viewport на объекты не будут их содержать ". жизнеобеспечение ", а это именно то, что вы хотите, потому что вы даже больше не используете этот пользовательский интерфейс, тем более создаваемые им виджеты.
Теперь предположим, что вы передали Viewport SharedPointer, удалили пользовательский интерфейс, а виджеты НЕ были собраны мусором! Зачем? потому что вьюпорт, который еще жив, имеет массив (вектор или список, что угодно), полный SharedPtrs для виджетов. Видовой экран фактически стал для них формой "жизнеобеспечения", даже если вы удалили пользовательский интерфейс, который контролировал виджеты для другого объекта пользовательского интерфейса.
Как правило, язык / система / фреймворк будет собирать мусор, если в памяти нет "сильной" ссылки на него. Представьте, что если бы во всем была сильная ссылка на что-либо, ничто не могло бы собрать мусор! Иногда вам нужно такое поведение, иногда нет. Если вы используете WeakPtr, и Shared/StrongPtrs не указывают на объект (только WeakPtrs), то объекты будут собираться мусором, несмотря на ссылки WeakPtr, а WeakPtrs (должен быть) установлен в NULL (или удален, или что-то).
Опять же, когда вы используете WeakPtr, вы в основном позволяете объекту, который вы ему даете, тоже иметь доступ к данным, но WeakPtr не будет препятствовать сборке мусора для объекта, на который он указывает, как это делает SharedPtr. Когда вы думаете, SharedPtr, думайте "жизнеобеспечение", WeakPtr, НЕТ "жизнеобеспечение". Сборка мусора (как правило) не будет происходить, пока объект не получит нулевую поддержку жизни.
Слабые ссылки могут, например, использоваться в сценариях кеширования - вы можете получить доступ к данным через слабые ссылки, но если вы не обращаетесь к данным в течение длительного времени или существует высокая нагрузка на память, GC может освободить их.
Причина сбора мусора вообще заключается в том, что в языке, подобном C, управление памятью полностью находится под явным контролем программиста, когда владение объектом передается, особенно между потоками или, что еще сложнее, между процессами, разделяющими память, избегая утечек памяти и висячие указатели могут стать очень трудными. Если это не было достаточно сложно, вам также придется иметь дело с необходимостью иметь доступ к большему количеству объектов, которые не поместятся в памяти за один раз, - вам нужно иметь способ освободить некоторые объекты на некоторое время, чтобы другие объекты могли может быть в памяти.
Так, некоторые языки (например, Perl, Lisp, Java) предоставляют механизм, в котором вы можете просто прекратить "использование" объекта, и сборщик мусора в конечном итоге обнаружит это и освободит память, используемую для объекта. Он делает это правильно, не беспокоясь о том, как программист может ошибиться (хотя есть много способов, которыми программисты могут облажаться).
Если вы концептуально умножаете количество обращений к объекту на время, необходимое для вычисления значения объекта, и, возможно, снова умножаете его на стоимость отсутствия доступного объекта или на размер объекта с момента сохранения Большой объект в памяти может препятствовать сохранению нескольких небольших объектов, вы можете классифицировать объекты по трем категориям.
Некоторые объекты настолько важны, что вы хотите явно управлять их существованием - они не будут управляться сборщиком мусора или никогда не должны собираться до тех пор, пока не будут явно освобождены. Некоторые объекты дешевы для вычисления, имеют небольшой размер, к ним редко обращаются или имеют сходные характеристики, которые позволяют собирать мусор в любое время.
Третий класс - объекты, которые являются дорогостоящими для пересчета, но могут быть пересчитаны, доступны довольно часто (возможно, для короткого периода времени), имеют большой размер и т. Д. - третий класс. Вы хотели бы хранить их в памяти как можно дольше, потому что они могут быть снова использованы повторно, но вы не хотите исчерпывать память, необходимую для критических объектов. Это кандидаты на слабые ссылки.
Вы хотите, чтобы эти объекты хранились как можно дольше, если они не конфликтуют с критическими ресурсами, но их следует отбрасывать, если для критического ресурса требуется память, поскольку при необходимости ее можно заново вычислить. Это шляпа слабые указатели для.
Примером этого могут быть картинки. Скажем, у вас есть фото веб-страница с тысячами картинок для отображения. Вам нужно знать, сколько фотографий выложить, и, возможно, вам нужно выполнить запрос к базе данных, чтобы получить список. Память для хранения списка из нескольких тысяч предметов, вероятно, очень мала. Вы хотите выполнить запрос один раз и сохранить его.
Тем не менее, вы можете физически показывать, возможно, несколько десятков картинок за раз на панели веб-страницы. Вам не нужно извлекать биты для картинок, на которые пользователь не может смотреть. Когда пользователь прокручивает страницу, вы соберете фактические биты для видимых картинок. Эти фотографии могут потребовать много мегабайт, чтобы показать их. Если пользователь прокручивает назад и вперед между несколькими позициями прокрутки, вам не нужно повторять эти мегабайты снова и снова. Но вы не можете хранить все фотографии в памяти все время. Таким образом, вы используете слабые указатели.
Если пользователь просто просматривает несколько картинок снова и снова, они могут оставаться в кеше, и вам не нужно их повторно загружать. Но если они прокручиваются достаточно, вам нужно освободить память, чтобы можно было извлечь видимые изображения. Со слабой ссылкой вы проверяете ссылку непосредственно перед ее использованием. Если он все еще действителен, вы используете его. Если это не так, вы делаете дорогой расчет (выборка), чтобы получить его.