2008-11-18 2 views
6

Сеть переполнена пояснениями "dreaded diamond problem". Итак, StackOverflow. Я думаю, что я понимаю этот бит, но я не могу перевести это знание в понимание чего-то подобного, но другого.C++ множественное виртуальное наследование по сравнению с COM

Мой вопрос начинается как чистый вопрос на C++, но ответ вполне может переходить в спецификацию MS-COM. общий вопрос проблема выходит:

class Base { /* pure virtual stuff */ }; 
class Der1 : Base /* Non-virtual! */ { /* pure virtual stuff */ }; 
class Der2 : Base /* Non-virtual! */ { /* pure virtual stuff */ }; 
class Join : virtual Der1, virtual Der2 { /* implementation stuff */ }; 
class Join2 : Join { /* more implementation stuff + overides */ }; 

Это не классическим решением алмазов. То, что здесь делает «виртуальный»?

Моя настоящая проблема заключается в попытке понять discussion over at our friends' place at CodeProject. Он включает в себя специальный класс для создания прозрачного контейнера для Flash-плеера.

Я думал, что попробую это место для удовольствия. Оказывается, следующее объявление сбрасывает ваше приложение с версией 10 проигрывателя Flash.

class FlashContainerWnd: virtual public IOleClientSite, 
          virtual public IOleInPlaceSiteWindowless, 
          virtual public IOleInPlaceFrame, 
          virtual public IStorage 

Отладка показывает, что при входе в реализации функций (QueryInterface и т.д.), от разных абонентов, я получаю разный «это» -указатель значения для различных вызовов. Но удаление "virtual" делает трюк! Никаких сбоев, и того же «этого» -потока.

Я хотел бы четко понимать, что происходит. Большое спасибо.

Приветствия Адам

+0

Я не умею пользоваться виртуальным наследованием. но в вашем приложении содержится листинг, который отсылает из файлов IOle или IStorage в FlashContainerWnd? – 2008-11-18 18:50:57

ответ

2

Я думаю, этот вопрос с вашим COM, например, что при добавлении виртуального ключевого слова вы говорите, что все интерфейсы * Иол имеют общую реализацию IUnknown. Чтобы реализовать это, компилятор должен создать несколько v-таблиц, поэтому вы различаете «это» значения в зависимости от производного класса, с которым он пришел.

COM требует, чтобы при вызове IQueryInterface на объект для IUnknown, что ВСЕХ интерфейсов выставленных объекта возвращают же IUnknown ... что эта реализация явно ломает.

Без виртуального наследования каждый IOle * номинально имеет свою собственную реализацию IUnknown. Однако, поскольку IUnknown является абстрактным классом и не имеет какого-либо хранилища, компилятор, и все реализации IUnknown приходят из FlashContainerWnd существует только одна реализация.

(КИ, так что последний бит звучит слабым ... возможно, кто-то с лучшим пониманием правил языка может объяснить это более ясно)

+0

да, я думаю, ты прав. Вы получаете IUnknown, а затем используете QueryInterface для получения требуемого интерфейса. Вы не бросаете COM-объект в обычный способ C++. – gbjbaanb 2008-11-18 22:53:23

+0

Спасибо, если я понимаю, что вы правильно отвечаете, я должен использовать «виртуальный», но факт в том, что я должен удалить его, чтобы остановить сбой. Поэтому я не понимаю, как это связано с моим вопросом. – Adam 2008-11-19 08:41:48

3

Виртуального наследования в первом примере, ничего не делать. Я бы сказал, что они компилируются по тому же коду, если они будут удалены.

Фактически унаследованный класс просто указывает компилятору, что он должен объединить более поздние версии Der1 или Der2. Поскольку в дереве наследования появляется только один из них, ничего не делается. Виртуальные машины не влияют на Base.

auto p = new Join2; 
static_cast<Base*>(static_cast<Der1*>(p)) != 
     static_cast<Base*>(static_cast<Der2*>(p)) 

Виртуальное наследование влияет только на следующий унаследованный класс и только для экземпляров, которые были делятся виртуальными. Это отстает от того, что вы ожидаете, но это ограничение на то, как скомпилированы классы.

class A {}; 
class B : virtual public A {}; 
class C : virtual public A {}; 
class D : public A {}; 
class E : virtual public A, public B, public C, public D {}; 
class F : public A, public B, public C, public D {}; 

F::A != F::B::A or F::C::A or F::D::A 
F::B::A == F::C::A 
F::D::A != F::B::A or F::C::A or F::A 

E::B::A == E::C::A == E::A 
E::D::A != E::B::A or E::C::A or E::D::A 

Одна из причин, по которым должны быть отмечены виртуальными в C и B вместо E или F, что C и B должны знать, чтобы не вызывать конструктор в. Обычно они инициализировали каждую из своих копий. Когда они участвуют в наследовании алмазов, они не будут. Но вы не можете перекомпилировать B и C, чтобы не создавать A. Это означает, что C и B должны знать заранее, чтобы создать код конструктора, где конструктор A не вызывается.

+0

Я не уверен, что вы подразумеваете под «экземплярами, которые были делятся виртуальными». Если вы имеете в виду виртуальное наследование subsequnet, то моя реальная проблема, к сожалению, доказывает ваш ответ неправильно. Потому что у меня только виртуальный на одном уровне. С виртуальным: сбой. Без виртуального: хорошо. – Adam 2008-11-19 08:54:10

0

Это немного датировано сейчас, но лучшая ссылка, которую я когда-либо встречал, касается внутренних элементов C++, это Lippman's Inside The C++ Object Model. Точные сведения о реализации могут не соответствовать выходу вашего компилятора, но понимание, которое оно обеспечивает, чрезвычайно ценно.

Вокруг стр. 96 приведено объяснение виртуального наследования, и оно конкретно касается проблемы с алмазом.

Я оставлю вас для ознакомления с деталями, но в основном использование виртуального наследования требует поиска в виртуальной таблице, чтобы найти базовый класс. Это не относится к нормальному наследованию, где местоположение базового класса можно вычислить во время компиляции.

(В последний раз я взял легкий путь и просто рекомендовал книгу, чтобы ответить на переполнение стека вопрос, который я получил проголосовал вверх значительно, так что давайте посмотрим, если это случится еще раз ... :)

0

Я думал, Просто попробуй свой пример. Я придумал:

#include "stdafx.h" 
#include <stdio.h> 

class Base 
{ 
public: 
    virtual void say_hi(const char* s)=0; 
}; 

class Der1 : public Base 
{ 
public: 
    virtual void d1()=0; 
}; 

class Der2 : public Base 
{ 
public: 
    virtual void d2()=0; 
}; 

class Join : virtual public Der1, virtual public Der2 
      // class Join : public Der1, public Der2 
{ 
public: 
    virtual void say_hi(const char* s); 
    virtual void d1(); 
    virtual void d2(); 
}; 

class Join2 : public Join 
{ 
    virtual void d1(); 
}; 

void Join::say_hi(const char* s) 
{ 
    printf("Hi %s (%p)\n", s, this); 
} 

void Join::d1() 
{} 

void Join::d2() 
{} 

void Join2::d1() 
{ 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Join2* j2 = new Join2(); 
    Join* j = dynamic_cast<Join*>(j2); 
    Der1* d1 = dynamic_cast<Der1*>(j2); 
    Der2* d2 = dynamic_cast<Der2*>(j2); 
    Base* b1 = dynamic_cast<Base*>(d1); 
    Base* b2 = dynamic_cast<Base*>(d2); 

    printf("j2: %p\n", j2); 
    printf("j: %p\n", j); 
    printf("d1: %p\n", d1); 
    printf("d2: %p\n", d2); 
    printf("b1: %p\n", b1); 
    printf("b2: %p\n", b2); 

    j2->say_hi("j2"); 
    j->say_hi(" j"); 
    d1->say_hi("d1"); 
    d2->say_hi("d2"); 
    b1->say_hi("b1"); 
    b2->say_hi("b2"); 

    return 0; 
} 

Он производит следующий вывод:

j2: 00376C10 
j: 00376C10 
d1: 00376C14 
d2: 00376C18 
b1: 00376C14 
b2: 00376C18 
Hi j2 (00376C10) 
Hi j (00376C10) 
Hi d1 (00376C10) 
Hi d2 (00376C10) 
Hi b1 (00376C10) 
Hi b2 (00376C10) 

Таким образом, при литье Join2 его базовых классов, вы можете получить различные указатели, но этот указатель передается say_hi () всегда одно и то же, как и ожидалось.

Так что, в основном, я не могу воспроизвести вашу проблему, что затрудняет ответ на ваш реальный вопрос.

Относительно ваты «виртуальный» делает, я нашел статью на wikipedia поучительно, хотя это тоже, кажется, сосредоточиться на проблеме алмазов

0

Как Caspin говорит, ваш первый пример не делает ничего полезного. Однако, что он будет делать, добавьте vpointer, чтобы сообщить производным классам, где найти классы, на которые он унаследовал.

Это исправляет любые бриллианты, которые вы теперь можете создать (а это не так), но поскольку структура классов теперь не является статичной, вы больше не можете использовать static_cast. Я незнаком с вовлеченным API, но то, что говорит Роб Уокер об IUnkown, может быть связано с этим.

Короче говоря, нормальное наследование следует использовать, если вам нужен ваш собственных BaseClass, которые не должны использоваться совместно с классами «родным братом»: (а представляет собой контейнер, б, в, г являются частями, что у каждого есть контейнер, e объединяет эти части (плохой пример, почему бы не использовать композицию?))

a a a 
| | | 
b c d <-- b, c and d inherit a normally 
\ |/
    e 

Хотя виртуальное наследование предназначено для совместного использования базового слоя. (a - транспортное средство, b, c, d - разные специализации транспортного средства, e объединяет их)

a 
/| \ 
b c d <-- b, c and d inherit a virtually 
\ |/
    d 
Смежные вопросы