2010-06-21 16 views
10

He leído alguna afirmación vaga de que virtual inheritance no proporciona la estructura de memoria requerida por COM, por lo que debemos usar la herencia normal. La herencia virtual se inventó para manejar el problema de diamantes.¿Por qué no podemos usar "herencia virtual" en COM?

¿Podría alguien mostrarme una ilustración de la diferencia de los detalles de la estructura de la memoria entre estos dos enfoques heredados? Y la razón clave por qué la herencia virtual no es adecuada para COM. Una imagen sería lo mejor.

Muchas gracias.

Respuesta

2

Las interfaces COM son bastante parecidas a las interfaces JAVA de una manera: no tienen miembros de datos. Esto significa que la herencia de interfaz es diferente a la herencia de clase cuando se usa herencia múltiple.

Para empezar, la herencia considerar no virtual con patrones de herencia en forma de diamante ...

  • B hereda una
  • C hereda una
  • D hereda B y C

Una instancia de D contiene dos instancias separadas de los miembros de datos de A. Eso significa que cuando un apuntador a A apunta a una instancia de D, necesita identificar qué instancia de A dentro de D significa - la punta r es diferente en cada caso, y los lanzamientos de puntero no son simples relés del tipo - la dirección también cambia.

Considere ahora el mismo diamante con herencia virtual. Las instancias de B, C y D contienen una única instancia de A. Si considera que B y C tienen un diseño fijo (incluida la instancia A), esto es un problema. Si el diseño de Bs es [A, x] y el diseño de Cs es [A, y], entonces [B, C, z] no es válido para D - contendría dos instancias de A. Lo que tiene que usar es algo así como [ A, B ', C', z] donde B 'es todo desde B excepto el A heredado, etc.

Esto significa que si tiene un puntero-a-B, no tiene un solo esquema para desreferenciar los miembros heredados de A. Encontrar esos miembros es diferente dependiendo de si el puntero apunta a un B puro o un B dentro de D o un B dentro de algo diferente. El compilador necesita alguna pista en tiempo de ejecución (tablas virtuales) para encontrar los miembros heredados de A. Terminas necesitando varios punteros a varias tablas virtuales en la instancia D, ya que hay un vtable para el B heredado y para el C heredado, etc., lo que implica un poco de sobrecarga de memoria.

La herencia simple no tiene estos problemas. El diseño de la memoria de las instancias se mantiene simple, y las tablas virtuales son más simples también. Es por eso que Java no permite herencia múltiple para las clases. En la herencia de la interfaz no hay miembros de datos, así que de nuevo estos problemas simplemente no surgen, no hay problema, que heredaron A-con-D, ni de diferentes maneras de encontrar A dentro de B, dependiendo de qué B particular pasa a estar dentro. Tanto COM como Java pueden permitir herencia múltiple de interfaces sin tener que manejar estas complicaciones.

EDITAR

me olvidaba decir - sin miembros de datos, no hay ninguna diferencia real entre la herencia virtual y no virtual. Sin embargo, con Visual C++, el diseño es probablemente diferente, incluso si no hay miembros de datos, utilizando las mismas reglas para cada estilo de herencia consistentemente independientemente de si hay miembros de datos presentes o no.

Además, el diseño de la memoria COM coincide con el diseño de Visual-C++ (para los tipos de herencia soportados) porque fue diseñado para hacer eso. No hay ninguna razón por la que COM no se haya diseñado para admitir herencia múltiple y virtual de "interfaces" con miembros de datos. Microsoft podría haber diseñado COM para admitir el mismo modelo de herencia que C++, pero optó por no hacerlo, y no hay ninguna razón por la que deberían haber hecho lo contrario.

El código COM antiguo a menudo se escribía en C, es decir, distribuciones de estructuras manuscritas que tenían que coincidir exactamente con el diseño de Visual-C++ para funcionar. Diseños para la herencia múltiple y virtual: bueno, no me ofrecería voluntariamente para hacerlo manualmente. Además, COM siempre fue lo suyo, con la intención de vincular el código escrito en muchos idiomas diferentes. Nunca se pretendió vincularlo a C++.

AÚN MÁS DE EDICIÓN

me di cuenta de que me perdí un punto clave.

En COM, el único problema de diseño que importa es la tabla virtual, que solo debe gestionar el envío de métodos. Existen diferencias significativas en el diseño, dependiendo de si se toma el enfoque virtual o no virtual, similar a la disposición de el objeto con miembros de datos ...

  • Para no virtual, el VTAB D contiene una A- dentro de B vtab y A-within-C vtab.
  • Para virtual, el A solo aparece una vez dentro de Ds vtable, pero el objeto contiene varios vtables y los modelos de puntero necesitan cambios de dirección.

Con interfaz de la herencia, esto es básicamente detalle de implementación - sólo hay una serie de implementaciones de métodos de A.

En el caso no virtual, las dos copias de la tabla virtual Un serían idénticos (llevando a las mismas implementaciones de método). Es una tabla virtual un poco más grande, pero la sobrecarga por objeto es menor y los lanzamientos del puntero son simplemente re-etiquetados (sin cambio de dirección). Es una implementación más simple y más eficiente.

COM no puede detectar el caso virtual porque no hay indicador en el objeto o vtable. Además, no tiene sentido apoyar ambas convenciones cuando no hay miembros de datos. Simplemente es compatible con la única convención simple.

+1

Me resulta muy incómodo obtener la aceptación de esta respuesta. ¿Cómo hago para que me la quiten? - Respondí detenidamente, editado para continuar, y hay algo aquí que probablemente explique un aspecto clave de por qué sucedió esto en los primeros días del COM, pero solo tengo una comprensión muy limitada y anticuada del COM, y esto responder simplemente no es exacto. En DCOM, simplemente no puede ser correcto, e incluso en días de "cuándo-era-simplemente-el-OLE2-back-end", una implementación de QueryInterface probablemente podría hacer lo que quiera en principio. – Steve314

+0

"_Single inheritance no tiene estos problemas." Obviamente, con single hay solo un vptr y vtable compartidos. "_En la herencia de interfaz no hay miembros de datos, por lo que nuevamente estos problemas simplemente no surgen_" si "estos problemas" se refieren a su descripción anterior de "Usted necesita varios punteros a varias tablas virtuales en la instancia D" y "implica un poco de memoria ", entonces esto obviamente está mal: el MI de las interfaces implica tantos vptr y vtables como interfaces heredadas, en C++ o en Java, si desea mantener la eficiencia del tiempo de ejecución de las llamadas a funciones virtuales de C++. – curiousguy

+0

"_ Olvidé decir que, sin miembros de datos, no existe una diferencia real entre la herencia virtual y no virtual._" Absolutamente no es correcto. Me temo que simplemente no entiendes la herencia en C++. "_Sin embargo, con Visual C++, el diseño es probablemente diferente, incluso si no hay miembros de datos_", el diseño ciertamente no es el mismo para 'struct D: B' y para' struct D: virtual B'.No veo cómo podrían tener un diseño similar, sin ser muy ineficientes. – curiousguy

4

En primer lugar, en COM el comportamiento de la herencia virtual siempre se utiliza. QueryInterface no puede devolver un valor diferente para, p. Ej. el IUnknown puntero base, dependiendo de qué clase derivada se usó para obtenerlo.

Pero tiene razón en que este no es el mismo mecanismo que la herencia virtual en C++. C++ no utiliza una función QueryInterface para la conversión ascendente, por lo que necesita otra forma de obtener el puntero de clase base.

El problema de diseño de la memoria se debe a que COM requiere que todos los métodos de una interfaz base puedan invocarse directamente utilizando un puntero de interfaz derivado. AddRef es un buen ejemplo. En COM, puede llamar al AddRef y pasar cualquier interfaz derivada como el puntero this. En C++, la implementación AddRef esperaría que este puntero fuera del tipo IUnknown* const. La diferencia es que en C++, la persona que llama encuentra el puntero base, mientras que en COM el destinatario realiza el ajuste para encontrar el puntero base, por lo que cada interfaz derivada necesita una implementación distinta (de QueryInterface, al menos) consciente del desplazamiento del derivado puntero de interfaz pasado al puntero base.

A primera vista, un compilador de C++ podría elegir, como un detalle de implementación, hacer que el destinatario realice el ajuste al igual que COM. Pero las reglas de la función de puntero a miembro no son compatibles con esta implementación de clases base virtuales.

4

Un COM coclass puede implementar múltiples interfaces pero cada interfaz individual debe implementar una tabla v con punteros a todos los de los métodos introducidos por sus interfaces 'base'. Como mínimo IUnknown. Si, por ejemplo, implementa IPersistFile, debe proporcionar una implementación de los tres métodos IUnknown e IPersist :: GetClassID. Y los métodos específicos de IPersistFile.

Lo que coincide con el comportamiento de la mayoría de los compiladores de C++ cuando implementan herencia múltiple no virtual. El compilador configura tablas v individuales para cada clase heredada (abstracta pura). Y lo llena de punteros de método para que un método de clase común implemente todos los métodos que las interfaces tienen en común. En otras palabras, no importa cuántas interfaces se implementen, todas son atendidas por un método de clase como QueryInterface, AddRef o Release.

Exactamente de la manera que desearía que funcionara. Tener una implementación de AddRef/Release hace que el recuento de referencias sea simple para mantener el objeto coclass vivo, sin importar cuántos punteros de interfaz diferentes entregue. QueryInterface es trivial de implementar, un simple molde proporciona el puntero de la interfaz a una tabla v con el diseño correcto.

La herencia virtual no es necesaria. Y lo más probable es que rompa COM porque las tablas v ya no tienen el diseño requerido. Lo cual es complicado para cualquier compilador, revise las opciones/vm para el compilador MSVC, por ejemplo. Ese COM tan asombrosamente es compatible con el comportamiento típico de un compilador de C++ es no un accidente.

Por cierto, todo esto choca con el ventilador cuando un coclass desea implementar múltiples interfaces que tienen un nombre de método en común que no pretende hacer lo mismo. Eso es muy bueno y difícil de tratar. Mencionado en ATL Internals (DAdvise?), Tristemente olvidé la solución.

+0

+1. También vale la pena señalar que los consumidores solo pueden usar el diseño designado para la interfaz que solicitaron, y nada más. A veces puede ser tentador hacer trampa, si está ejecutando un solo apartamento y controla tanto el objeto COM como el consumidor. Sin embargo, las llamadas entre departamentos/DCOM no funcionarán y fallarán horriblemente. Las interfaces sujetas a marshaling son en realidad proxies en lugar de objetos remotos, que casi nunca apuntan al objeto proveedor original. – meklarian

Cuestiones relacionadas