Почему мы не можем использовать “виртуальное наследование” в COM?

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

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

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

10
задан Jason S 21 June 2010 в 03:44
поделиться

3 ответа

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

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

  • B наследует A
  • C наследует A
  • D наследует B и C

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

Теперь рассмотрим тот же алмаз с виртуальным наследованием. Все экземпляры 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 внутри D или B внутри чего-то еще. Компилятору нужны подсказки во время выполнения (виртуальные таблицы), чтобы найти элементы, унаследованные от A.В конечном итоге вам понадобятся несколько указателей на несколько виртуальных таблиц в экземпляре D, так как есть vtable для унаследованного B и для унаследованного C и т. Д., Что подразумевает некоторые накладные расходы на память.

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

РЕДАКТИРОВАТЬ

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

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

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

ЕЩЕ РЕДАКТИРОВАТЬ

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

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

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

При наследовании интерфейсов это в основном детали реализации - есть только один набор реализаций метода для A.

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

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

2
ответ дан 4 December 2019 в 01:29
поделиться

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

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

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

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

4
ответ дан 4 December 2019 в 01:29
поделиться

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

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

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

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

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

4
ответ дан 4 December 2019 в 01:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: