четверг, 27 января 2011 г.

Об объектах с нулевым счётчиком ссылок

Это перевод On objects with a reference count of zero. Автор: Реймонд Чен. Примечание: этот пост сильно отличается от оригинала.

В обсуждении предыдущего поста один комментатор заявил:
Когда объект создаётся первый раз, его счётчик ссылок должен быть равен 0, а _AddRef должна вызываться позже в какой-то момент (наверное, через QueryInterface).
(прим.пер.: это не так в Delphi; счётчик ссылок равен 1 во время создания объекта - он становится равен 0 лишь в самом конце, когда вы сохраняете ссылку в объект, а не интерфейс)

Если вы создаёте объект со счётчиком ссылок равным нулю, то вы играете со спичками. Для начала, когда объект создаётся, его счётчик ссылок не должен быть равен нулю - ведь ссылку на объект имеет тот, кто создаёт объект! Вспомните правило COM для ссылок: если функция возвращает ссылку (обычно ссылку на интерфейс), то счётчик ссылок увеличивается с учётом созданной ссылки. Если вы посмотрите на конструктор как на функцию, то он должен будет возвращать увеличенный счётчик ссылок.

Если вы предпочитаете играть со спичками, то вы можете обжечься с аналогичным кодом:
// Статический метод создания объекта
class function TMyObject.Create(riid: REFIID; out ppvObj): HRESULT;
var
  pObj: TMyObject;
begin
  Pointer(ppvObj) := nil;
  pobj := TMyObject.Create;
  Result = pobj.Initialize; // опасно!
  if SUCCEEDED(Result)
    Result = pobj.QueryInterface(riid, ppvObj);
  if Failed(Result) then
    pObj.Free;
end;
Заметьте, что вы инициализируете объект пока его счётчик ссылок равен 0. Этим вы оказываетесь в той же красной зоне, что и с деструктором - и у вас появляются те же проблемы:
function TMyObject.Load: HRESULT;
var
  spstm: IStream;
  spows: IObjectWithSite;
begin
  Result := GetLoadStream(spstm);
  if SUCCEEDED(Result) then
  begin
    Supports(spstm, IObjectWithSite, spows);
    if Assigned(spows) then
      spows.SetSite(Self);
    Result := LoadFromStream(spstm);
    if Assigned(spows)
      spows.SetSite(nil);
  end;
end;

function TMyObject.Initialize: HRESULT;
begin
  Result := Load;
end;
Объект, который сохраняет себя во время уничтожения, вполне может и загружать себя при создании. И тогда вы вляпываетесь в ту же самую проблему. Вызов IObjectWithSite.SetSite(Self) увеличит счётчик с нуля до 1, а затем вызов IObjectWithSite.SetSite(nil) сбросит счётчик до нуля, приводя к ошибочному удалению объекта во время инициализации.

Затем статический метод TMyObject.Create не осознает этого и продолжит выполнение, вызывая QueryInterface, чтобы увеличить счётчик с нуля до единицы и вернуть интерфейс вызывающему. К сожалению, он будет делать это на объекте, который уже удалён.

Вот что происходит, когда вы играетесь с объектами с нулевым счётчиком ссылок: он может исчезнуть в любой момент, когда вы ослабляете контроль. Объекты следует создавать со счётчиком ссылок равным единице, а не нулю.

Прим.пер.: здесь ещё была куча текста по ATL - удалил без перевода, как не представляющее для меня интереса.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.