Сущность технологии СОМ. Библиотека программиста | страница 55
HRESULT _stdcall QueryInterface(REFIID riid, void** ppv);
Как было отмечено ранее, клиенты вызывают QueryInterface, передавая объекту указатель на интерфейсный указатель в качестве второго параметра вместе с IID, который определяет тип ожидаемого интерфейсного указателя:
IPug *pPug = 0; hr = punk->QueryInterface(IID_IPug, (void**)&pPug);
К сожалению, для компилятора C++ таким же правильным выглядит и следующее:
IPug *pPug = 0; hr = punk->QueryInterface(IID_ICat, (void**)&pPug);
Даже еще более хитроумный вариант компилируется без ошибок:
IPug *pPug = 0; hr = punk->QueryInterface(IID_IPug, (void**)pPug);
Исходя из того, что правила наследования неприменимы к указателям, такое альтернативное определение QueryInterface нe облегчает проблему:
HRESULT QueryInterface(REFIID riid, IUnknown** ppv);
так как неявное приведение типа к родительскому типу (upcasting) применимо только к объектам и указателям на объекты, а не к указателям на указатели на объекты:
IDerived **ppd; IBase **ppb = ppd;
// illegal
// неверно
To же ограничение применимо в равной мере и к ссылкам на указатели. Следующее альтернативное определение вряд ли более удобно для использования клиентами:
HRESULT QueryInterface(const IID& riid, void* ppv);
так как позволяет клиентам отказаться от приведения типа (cast). К сожалению, это решение не уменьшает количества ошибок (обе из предшествующих ошибок все еще возможны), а устраняя необходимость приведения, уничтожает и видимый индикатор того, что устойчивость типов C++ может оказаться в опасности. Если желательна семантика QueryInterface, то выбор типов аргументов, сделанный корпорацией Microsoft, по крайней мере, разумен, если не надежен или изящен. Простейший путь избежать ошибок, связанных c QueryInterface,– это всегда быть уверенным в том, что IID соответствует типу указателя интерфейса, который проходит как второй параметр QueryInterface. На самом деле первый параметр QueryInterface описывает «форму» типа указателя второго параметра. Их связь может быть усилена на этапе компиляции с помощью такого макроса предпроцессора С:
#define IID_PPV_ARG(Type, Expr) IID_##type,
reinterpret_cast
С помощью этого макроса[1] компилятор будет уверен в том, что выражение, использованное в приведенном ниже вызове QueryInterface, имеет правильный тип и что используется соответствующий уровень изоляции (indirecton):
IPug *pPug = 0; hr = punk->QueryInterface(IID_PPV_ARG(IPug, &pPug));