Шрифт:
LONG res = -mcRef;
if (res == 0) delete this;
return res;
}
Для кэширования обновленного счетчика ссылок необходимо использовать временную переменную, так как нельзя обращаться к элементам данных объекта после того, как объект уже уничтожен.
Заметим, что показанные реализации Addref и Release используют собственные операторы инкремента и декремента (увеличения и уменьшения на единицу). Для простой реализации это весьма разумно, так как СОМ не допускает более одного потока для обращения к объекту до тех пор, пока конструктор не обеспечит явный многопоточный доступ (почему и как конструктор сделает это, подробно описано в главе 5). В случае объектов, доступных в многопоточной среде, для автоматического подсчета ссылок следует использовать подпрограммы Win32 InterlockedIncrement/InterlockedDecrement:
STDMETHODIMP(ULONG) AddRef(void)
{
return InterlockedIncrement(&mcRef);
}
STDMETHODIMP(ULONG) Release(void)
{
LONG res = InterlockedDecrement(&mcRef);
if (res == 0) delete this; return res;
}
Этот код несколько менее эффективен, чем версии, использующие собственные операторы C++. Но, вообще говоря, разумнее использовать менее эффективные варианты InterlockedIncrement / InterlockedDecrement, так как известно, что они надежны во всех ситуациях и освобождают разработчика от необходимости сохранять две версии практически одинакового кода.
Показанные выше реализации AddRef и Release предполагают, что объект может размещаться только в динамически распределяемой области памяти (в «куче») с использованием С++-оператора new. В определении класса деструктор сделан защищенной операцией для обеспечения того, чтобы ни один экземпляр класса не был определен никаким другим способом. Однако иногда желательно иметь объекты, не размещенные в «куче». Для этих объектов вызов delete в последнем вызове Release был бы гибельным. Так как единственной причиной для того, чтобы объект в первую очередь поддерживал счетчик ссылок, была необходимость вызова delete this, допустимо оптимизировать счетчик ссылок для объектов, не содержащихся в динамически распределяемой области памяти:
STDMETHODIMP(ULONG) GlobalVar::AddRef(void)
{
return 2;
// any non-zero value is legal
// допустима любая ненулевая величина
}
STDMETHODIMP(ULONG) GlobalVar::Release (void)
{
return 1;
// any non-zero value is legal
// допустима любая ненулевая величина
}
Эта реализация использует тот факт, что результаты AddRef и Release служат только для сведения и не обязаны быть точными.
При наличии реализации AddRef и Release единственным еще не реализованным методом из IUnknown остается QueryInterface. Его реализации должны отслеживать иерархию типов объекта и использовать статические приведения типов для возврата правильного типа указателя для всех поддерживаемых интерфейсов. Для определения класса PugCat, рассмотренного ранее, следующий код является корректной реализацией QueryInterface : STDMETHODIMP
PugCat::QueryInterface(REFIID riid, void **ppv)
{
assert(ppv != 0);
// or return EPOINTER in production
// или возвращаем EPOINTER в реальный продукт
if (riid == IIDIPug) *ppv = staticcast<IPug*>(this);
else if (riid == IIDIDog) *ppv = staticcast<IDog*>(this);
else if (riid == IIDIAnimal)
// cat or pug?
// кот или мопс?
*ppv == staticcast<IDog*>(this);
else if (riid == IIDIUnknown)
// cat or pug?
// кот или мопс?
*ppv = staticcast<IDog*>(this);
else if (riid == IIDICat) *ppv = staticcast<ICat*>(this);
else
{
// unsupported interface
// неподдерживаемый интерфейс
*ppv = 0;
return ENOINTERFACE;
}
// if we reach this point, *ppv is non-null
// and must be AddRef'ed (guideline A2)
// если мы дошли до этого места, то *ppv ненулевой
// и должен быть обработан AddRef'ом ( принцип A2)