У Jeff Atwood есть сообщение на этом здесь: http://www.codinghorror.com/blog/archives/000229.html
Он в конечном счете пошел с Клавиатурой Редактирования Pro, потому что "На основе моей предшествующей истории использования, я чувствовал, что EditPad Pro был лучшим соответствием: это довольно быстро на файлах крупного текста, имеет лучшую среди аналогов поддержку regex, и это не симулирует быть IDE".
Проблема в том, что * ppv
обычно является void *
- прямое присвоение этого
ему просто возьмет существующий этот указатель
и присвоить ему значение * ppv
(поскольку все указатели могут быть преобразованы в void *
).
Это не проблема с одиночным наследование, потому что при одиночном наследовании базовый указатель всегда один и тот же для всех классов (потому что vtable просто расширяется для производных классов).
Однако - для множественного наследования вы фактически получаете несколько базовых указателей, в зависимости от того, какой вид класса, о котором вы говорите! Причина этого в том, что при множественном наследовании вы не можете просто расширить vtable - вам нужно несколько vtables в зависимости от того, о какой ветке вы говорите.
[0] @A vtable
[1] a0
[2] b0
[3] @C vtable
[4] c0
[5] d0
Обратите внимание, что если вы обрабатываете таблицу D
vtable как A
, она будет работать (это совпадение - на нее нельзя полагаться). Однако - если вы обрабатываете vtable D
vtable как C
, когда вы вызываете c0
(который компилятор ожидает в слоте 0 vtable), вы внезапно будете вызов a0
!
Когда вы вызываете c0
на D
, компилятор фактически передает поддельный указатель this
, который имеет vtable, которая выглядит так, как должна для C
.
Поэтому, когда вы вызываете функцию C
на D
, ей необходимо настроить vtable на наведите курсор на середину объекта D
(в таблице @C
vtable) перед вызовом функции.
D
vtable как C
, когда вы вызываете c0
(который компилятор ожидает в слоте 0 vtable), вы внезапно будете вызов a0
!
Когда вы вызываете c0
на D
, компилятор фактически передает поддельный указатель this
, который имеет vtable, которая выглядит так, как должна для C
.
Поэтому, когда вы вызываете функцию C
на D
, ей необходимо настроить vtable на наведите курсор на середину объекта D
(в таблице @C
vtable) перед вызовом функции.
D
vtable как C
, когда вы вызываете c0
(который компилятор ожидает в слоте 0 vtable), вы внезапно будете вызов a0
!
Когда вы вызываете c0
на D
, компилятор фактически передает поддельный указатель this
, который имеет vtable, которая выглядит так, как должна для C
.
Поэтому, когда вы вызываете функцию C
на D
, ей необходимо настроить vtable на наведите курсор на середину объекта D
(в таблице @C
vtable) перед вызовом функции.
Вы занимаетесь программированием на COM, поэтому есть несколько вещей, которые нужно вспомнить о вашем коде, прежде чем смотреть, почему QueryInterface
реализован именно так.
IInterface1
и IInterface2
происходят от IUnknown
, и давайте предположим, что ни один из них не является потомком другого. QueryInterface (IID_IUnknown, (void **) & intf)
для вашего объекта, intf
будет объявлен как тип IUnknown *
. QueryInterface
может быть вызван через любой из них. Поскольку точка № 3, значение this
в вашем определении QueryInterface
может варьироваться. Вызов функции через указатель IInterface1
, и этот
будет иметь другое значение, чем если бы он был вызван через указатель IInterface2
. В любом случае this
будет содержать действительный указатель типа IUnknown *
из-за точки №1, поэтому, если вы просто назначите * ppv = this
, вызывающий будут счастливы, с точки зрения C ++ . Вы сохраните значение типа IUnknown *
в переменной того же типа (см. Пункт 2), так что все в порядке.
Однако COM имеет более строгие правила, чем обычный C ++ .В частности, он требует, чтобы любой запрос к интерфейсу IUnknown
объекта должен возвращать тот же указатель, независимо от того, какое «представление» этого объекта использовалось для вызова запроса. Следовательно, вашему объекту недостаточно всегда присваивать просто this
в * ppv
. Иногда вызывающие абоненты получали версию IInterface1
, а иногда - версию IInterface2
. Правильная реализация COM должна гарантировать стабильные результаты. Обычно для всех поддерживаемых интерфейсов используется лестничная диаграмма if
- else
, но одно из условий проверяет наличие двух интерфейсов вместо одного, второе - IUnknown
:
if (iid == IID_IUnknown || iid == IID_IInterface1) {
*ppv = static_cast<IInterface1*>(this);
} else if (iid == IID_IInterface2) {
*ppv = static_cast<IInterface2*>(this);
} else {
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
Неважно, с каким интерфейсом сгруппирована проверка IUnknown
, если группировка не изменится, пока объект все еще существует, но вам действительно придется выйти из ваш способ сделать это возможным.