2010-06-21 4 views
10

Я прочитал некоторое неопределенное утверждение о том, что virtual inheritance не обеспечивает структуру памяти, требуемую COM, поэтому мы должны использовать обычное наследование. Виртуальное наследование изобретено для обработки проблемы с алмазами .Почему мы не можем использовать «виртуальное наследование» в COM?

Может ли кто-нибудь показать мне иллюстрацию различия деталей структуры памяти между этими двумя подходами наследования? И ключевая причина : Почему виртуальное наследование не подходит для COM. Изображение было бы лучше.

Большое спасибо.

ответ

2

COM-интерфейсы в некотором смысле похожи на интерфейсы JAVA - у них нет данных. Это означает, что наследование интерфейса отличается от наследования классов при использовании множественного наследования.

Для начала, рассмотрим невиртуальном наследования с ромбовидными узорами наследования ...

  • Б наследует
  • C наследует
  • D наследует В и С

Экземпляр D содержит два отдельных экземпляра элементов данных A. Это означает, что когда указатель-a указывает на экземпляр D, он должен идентифицировать, какой экземпляр A внутри D он означает, - pointe r различается в каждом случае, а указатели-указатели не являются простыми релябельными типами - также меняется адрес.

Теперь рассмотрим тот же алмаз с виртуальным наследованием. Экземпляры B, C и D содержат один экземпляр A. Если вы считаете, что B и C имеют фиксированный макет (включая экземпляр A), это проблема. Если макет Bs [A, x] и макет Cs [A, y], тогда [B, C, z] недействителен для D - он будет содержать два экземпляра A. То, что вы должны использовать, это что-то вроде [ A, B ', C', z], где B '- все, начиная с B, за исключением унаследованных A и т. Д.

Это означает, что если у вас есть указатель на B, у вас нет единой схемы разыменования члены, унаследованные от A. Поиск этих членов различается в зависимости от того, указывает ли указатель на чистый B или B-in-D или B-in-something-else. Компилятору требуется некоторый ключ времени выполнения (виртуальные таблицы), чтобы найти унаследованные-из-членов. В конечном итоге вам понадобится несколько указателей на несколько виртуальных таблиц в экземпляре D, поскольку theres vtable для унаследованного B и для унаследованного C и т. Д., Что подразумевает некоторые издержки памяти.

Одиночное наследование не имеет этих проблем. Макет памяти экземпляров остается простым, а виртуальные таблицы также проще. Вот почему Java запрещает множественное наследование для классов. В наследовании интерфейсов нет элементов данных, поэтому снова эти проблемы просто не возникают - нет проблемы с унаследованным-A-with-D, а также разными способами найти A-in-B в зависимости от того, что этот конкретный B оказывается, внутри. Оба COM и Java могут допускать множественное наследование интерфейсов без необходимости обработки этих осложнений.

EDIT

Я забыл сказать - без элементов данных, нет никакой разницы между виртуальным и не виртуальным наследованием. Однако с Visual C++ макет, вероятно, отличается, даже если нет элементов данных - с использованием одинаковых правил для каждого стиля наследования, независимо от того, присутствуют ли какие-либо элементы данных или нет.

Кроме того, макет памяти COM соответствует макету Visual C++ (для поддерживаемых типов наследования), поскольку он был разработан для этого. Нет причин, по которым COM не мог быть разработан для поддержки множественного и виртуального наследования «интерфейсов» с элементами данных. Microsoft могла бы спроектировать COM для поддержки той же модели наследования, что и C++, но не выбрала - и нет причин, по которым они должны были сделать иначе.

Ранний код COM часто записывался на языке C, что означает рукописные макеты структур, которые должны были точно соответствовать макету Visual C++ для работы. Макеты для множественного и виртуального наследования - ну, я бы не стал добровольно делать это вручную. Кроме того, COM всегда был своей собственностью, предназначенный для связи кода, написанного на разных языках. Это никогда не предназначалось для привязки к C++.

ЕЩЕ БОЛЕЕ РЕДАКТИРОВАНИЕ

Я понял, что пропустил ключевой момент.

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

  • Для невиртуального, Д vtab содержит A- внутри-B vtab и A-in-C vtab.
  • Для виртуальных, A встречается только один раз внутри Ds vtable, но объект содержит несколько vtables, а указатели-указатели нуждаются в изменениях адреса.

С интерфейсом-наследованием, это в основном деталь реализации - есть только один набор реализаций методы для А.

В невиртуальном случае две копии виртуальной таблицы А будет идентичной (что приводит к реализации того же метода). Его немного большая виртуальная таблица, но накладные расходы на объект меньше, а указатели-указатели - это только переименование типа (без изменения адреса). Это более простая и эффективная реализация.

COM не может обнаружить виртуальный случай, потому что нет индикатора в объекте или vtable. Кроме того, нет смысла поддерживать обе конвенции, когда нет элементов данных. Он просто поддерживает одно простое соглашение.

+1

Мне очень неудобно получить согласие на этот ответ - как я могу его отнять? - ответил я, размышляя, отредактировал это, и здесь есть что-то, что, вероятно, объясняет ключевой аспект того, почему это произошло в первые дни COM, но у меня только очень ограниченное и датированное понимание COM, и это ответ просто не является точным. В DCOM это просто не может быть правильным, и даже в дни, когда он был только-OLE2-back-end, реализация QueryInterface, вероятно, могла бы делать все, что ему нравится в принципе. – Steve314

+0

"_Настоящее наследование не имеет этих проблем." Очевидно, что с одним только один общий vptr и vtable. «В наследовании интерфейса нет элементов данных, поэтому снова эти проблемы просто не возникают», «если« эти проблемы »относятся к вашему предыдущему описанию« В конечном итоге вам нужно несколько указателей на несколько виртуальных таблиц в экземпляре D »и« подразумевая некоторые издержки памяти ", то это, очевидно, неверно: MI интерфейсов подразумевает столько vptr и vtables, сколько унаследованных интерфейсов, на C++ или Java, если вы хотите сохранить эффективность во время выполнения виртуальных вызовов C++. – curiousguy

+0

«Я забыл сказать - без данных, нет реальной разницы между виртуальным и не виртуальным наследованием.» «Абсолютно неверно. Я боюсь, вы просто не понимаете наследование в C++. «Однако, с Visual C++, макет, вероятно, отличается, даже если нет элементов данных». Макет, конечно же, не одинаковый для 'struct D: B' и для' struct D: virtual B'.Я не вижу, как они могут иметь подобный макет, не будучи очень неэффективным. – curiousguy

4

Во-первых, в COM всегда используется поведение виртуального наследования. QueryInterface не может вернуть другое значение, например. IUnknown базовый указатель в зависимости от того, какой производный класс использовался для его получения.

Но вы правы, что это не тот же механизм, что и виртуальное наследование в C++. C++ не использует функцию QueryInterface для повышения, поэтому ей нужен другой способ получения указателя базового класса.

Проблема с макетом памяти вызвана тем, что COM требует, чтобы все методы базового интерфейса могли быть вызваны непосредственно с использованием производного указателя интерфейса. AddRef - хороший пример. В COM вы можете позвонить AddRef и передать любой производный интерфейс в качестве указателя this. В C++ реализация AddRef ожидала бы, что этот указатель будет иметь тип IUnknown* const. Разница заключается в том, что в C++ вызывающий находит базовый указатель, тогда как в COM вызываемая команда выполняет настройку, чтобы найти базовый указатель, поэтому каждому производному интерфейсу требуется четкая реализация (QueryInterface, по крайней мере), осведомленная о смещении от производного указатель интерфейса, переданный в базовый указатель.

На первый взгляд, компилятор C++ может выбрать в качестве детали реализации, чтобы вызывающая команда выполняла настройку так же, как и COM. Но правила-указатели-члены-члены несовместимы с этой реализацией виртуальных базовых классов.

4

COM-компонент может реализовывать несколько интерфейсов, но каждый отдельный интерфейс должен реализовывать v-таблицу с указателями на все методов, созданных его «базовыми» интерфейсами. Как минимум IUnknown. Если он, скажем, реализует IPersistFile, то он должен обеспечить реализацию трех методов IUnknown, а также IPersist :: GetClassID. И специальные методы IPersistFile.

Что происходит в соответствии с поведением большинства компиляторов C++ при реализации не виртуального множественного наследования. Компилятор устанавливает отдельные v-таблицы для каждого унаследованного (чистого абстрактного) класса. И заполняет его указателями на методы, так что один общий метод класса реализует все методы, которые имеют общие интерфейсы. Другими словами, независимо от того, сколько интерфейсов реализовано, все они обслуживаются одним классом, таким как QueryInterface, AddRef или Release.

Точно так, как вы хотите, чтобы он работал. Наличие одной реализации AddRef/Release делает подсчет ссылок простым, чтобы сохранить объект coclass живым, независимо от того, сколько разных указателей интерфейса вы раздаете. QueryInterface тривиально для реализации, простое литье предоставляет указатель интерфейса на v-таблицу с правильной компоновкой.

Виртуальное наследование не требуется. И, скорее всего, он сломает COM, потому что v-таблицы больше не имеют требуемого макета. Что сложно для любого компилятора, просмотрите параметры/vm для компилятора MSVC, например. Этот COM, столь странно совместимый с типичным поведением компилятора C++, является не авария.

Btw, это все попадает в вентилятор, когда coclass хочет реализовать несколько интерфейсов, имеющих общее имя метода, которое не предназначено для того, чтобы делать то же самое. Это довольно большая проблема и трудно справиться. Упомянуто в ATL Internals (DAdvise?), Я, к сожалению, забыл о решении.

+0

+1. Также стоит отметить, что потребители могут использовать только назначенный макет для интерфейса, который они запросили, и не более того. Иногда может возникнуть соблазн обмануть, если вы работаете в одиночной квартире и контролируете как объект COM, так и потребитель. Тем не менее, вызовы межсетевых/DCOM не будут работать и будут терпеть неудачу. Интерфейсы, подлежащие маршалингу, фактически являются прокси-серверами вместо удаленных объектов, которые почти никогда не указывают на исходный объект-поставщик. – meklarian

Смежные вопросы