Правила управления счетчиками ссылок
Использование счетчика ссылок для управления временем существования объекта позволяет нескольким клиентам получать и освобождать доступ к одному объекту без необходимости координировать друг с другом при управлении временем существования объекта. Если клиентский объект соответствует определенным правилам использования, объект, фактически, обеспечивает это управление. Эти правила определяют, как управлять ссылками между объектами. (COM не указывает внутренние реализации объектов, хотя эти правила являются разумной отправной точкой для политики в объекте.)
Концептуально указатели интерфейса можно рассматривать как находящиеся в переменных указателя, которые включают все внутренние вычислительные состояния, содержащие указатель интерфейса. Это будет включать переменные в расположениях памяти, во внутренних регистрах процессора, а также в созданных программистом и компилятором переменных. Назначение или инициализация переменной указателя включает создание новой копии уже существующего указателя. Где была одна копия указателя в какой-то переменной (значение, используемое в назначении или инициализации), теперь два. Назначение переменной указателя уничтожает копию указателя, в настоящее время в переменной, как и разрушение самой переменной. (То есть, область, в которой найдена переменная, например кадр стека, уничтожается.)
С точки зрения com-клиента счетчик ссылок всегда выполняется для каждого интерфейса. Клиенты никогда не должны предполагать, что объект использует один и тот же счетчик для всех интерфейсов.
По умолчанию необходимо вызвать AddRef для каждой новой копии указателя интерфейса, а выпуск должен вызываться для каждого уничтожения указателя интерфейса, за исключением случаев, когда в противном случае допускаются следующие правила:
- Встроенные параметры для функций. Вызывающий объект должен вызвать AddRef в параметре, так как он будет выпущен (с вызовом выпуска) в реализуемом коде, когда значение out хранится поверх него.
- Получение глобальной переменной. При создании локальной копии указателя интерфейса из существующей копии указателя в глобальной переменной необходимо вызвать AddRef в локальной копии, так как другая функция может уничтожить копию в глобальной переменной, пока локальная копия по-прежнему действительна.
- Новые указатели синтезировались из "тонкого воздуха". Функция, которая синтезирует указатель интерфейса с помощью специальных внутренних знаний, а не получения его из другого источника, должна сначала вызвать AddRef на вновь синтезированном указателе. Важными примерами таких подпрограмм являются процедуры создания экземпляров, реализации QueryInterface и т. д.
- Получение копии внутреннего хранимого указателя. Когда функция получает копию указателя, хранящегося внутри вызываемого объектом, код этого объекта должен вызывать AddRef на указателе перед возвратом функции. После получения указателя исходный объект не имеет другого способа определения того, как его время существования связано с внутренне хранимой копией указателя.
Единственным исключениям по умолчанию требуется, чтобы управляемый код знал связи времени существования двух или более копий указателя на один и тот же интерфейс объекта и просто убедитесь, что объект не уничтожен, разрешая ему переходить к нулю. Как правило, существует два случая, как показано ниже.
- Если одна копия указателя уже существует, а вторая создается впоследствии, а затем уничтожается, пока первая копия по-прежнему существует, вызовы AddRef и Release для второй копии могут быть опущены.
- Если одна копия указателя существует, а вторая создается, а затем первый уничтожается до второго, вызовы AddRef для второй копии и выпуска первой копии могут быть опущены.
Ниже приведены конкретные примеры этих ситуаций, первые два особенно распространенных:
- В параметрах для функций. Время существования копии указателя интерфейса, передаваемого в качестве параметра функции, вложено в значение указателя, используемого для инициализации значения, поэтому нет необходимости в отдельном счетчике ссылок для параметра.
- Исходящие параметры из функций, включая возвращаемые значения. Чтобы задать параметр, функция должна иметь стабильную копию указателя интерфейса. При возврате вызывающий объект отвечает за освобождение указателя. Поэтому параметр out не нуждается в отдельном счетчике ссылок.
- Локальные переменные. Реализация метода управляет временем существования каждой из переменных указателя, выделенных на кадре стека, и может использовать это для определения того, как опустить избыточные пары AddRef/Release.
- Обратные указатели. Некоторые структуры данных содержат два объекта, каждый из которых указывает на другой. Если время существования первого объекта, как известно, содержит время существования второго, не обязательно иметь количество ссылок на указатель второго объекта на первый объект. Часто избегая этого цикла, важно поддерживать соответствующее поведение размещения сделки. Однако неотчетные указатели следует использовать с крайней осторожностью, так как часть операционной системы, которая обрабатывает удаленную обработку, не имеет способа знать об этой связи. Таким образом, почти во всех случаях, если указатель назад видит второй объект "друг" первого указателя (таким образом, избегая цикличности) является предпочтительным решением. Архитектура подключенных объектов COM, например, использует этот подход.
При реализации или использовании ссылочных объектов может быть полезно применять искусственные счетчики ссылок, которые гарантируют стабильность объектов во время обработки функции. При реализации метода интерфейса можно вызвать функции, которые могут привести к уменьшению количества ссылок объекту, что приводит к преждевременному выпуску объекта и сбою реализации. Надежный способ избежать этого заключается в том, чтобы вставить вызов AddRef в начале реализации метода и связать его с вызовом Release непосредственно перед возвратом метода.
В некоторых ситуациях возвращаемые значения AddRef и Release могут быть неустойчивыми и не должны полагаться на них. Они должны использоваться только для отладки или диагностики.
См. также