19

La herencia simple es fácil de implementar. Por ejemplo, en C, la herencia puede ser simulado como:¿Cómo se implementa la herencia múltiple de C++?

struct Base { int a; } 
struct Descendant { Base parent; int b; } 

Pero con la herencia múltiple, el compilador tiene que organizar varias padres dentro de la clase de nueva construcción. ¿Cómo se hace?

El problema que veo surgir es: ¿deberían organizarse los padres en AB o BA, o tal vez de otra manera? Y luego, si hago una conversión:

SecondBase * base = (SecondBase *) &object_with_base1_and_base2_parents; 

El compilador debe considerar si alterar o no el puntero original. Se requieren cosas complicadas similares con los virtuales.

+0

http://en.wikipedia.org/wiki/Diamond_problem – Dario

+0

La simulación C se olvida del puntero VTable (detalle de implementación). –

+1

@Dario: este artículo trata sobre los problemas de sobrecarga en herencia múltiple pero no contiene nada sobre el diseño del objeto y la conversión de objetos en C++. – mmmmmmmm

Respuesta

10

El siguiente trabajo del creador de C++ describe una posible implementación de la herencia múltiple:

Multiple Inheritance for C++ - Bjarne Stroustrup

+3

Enlace alternativo: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.23.4735&rep=rep1&type=pdf –

+0

Después leyendo ambos, dependiendo de lo que está buscando. Este (el del _Stroustrup_) es bastante detallado y cubre la teoría. El que propone _Nemanja_ a continuación (el de _MSDN_) es más simple y cubre una implementación real. –

+0

Ambos enlaces están muertos ahora, pero https://www.usenix.org/publications/compsystems/1989/fall_stroustrup.pdf funciona para mí. –

5

Hubo this pretty old MSDN article sobre cómo se implementó en VC++.

+1

Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace de referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página vinculada cambia. - [De la opinión] (/ reseña/mensajes de baja calidad/18125623) – CDspace

0

Depende totalmente del compilador cómo se hace, pero creo que generalmente se hace a través de una estructura jerárquica de tablas.

1

Los padres están dispuestos en el orden en que están especificados:

class Derived : A, B {} // A comes first, then B 

class Derived : B, A {} // B comes first, then A 

Su segundo caso se maneja de una manera específica del compilador. Un método común es usar punteros que son más grandes que el tamaño del puntero de la plataforma para almacenar datos adicionales.

+0

Esto es probablemente común, pero no creo que sea obligatorio. –

+0

Lo he visto de otra manera. Todo depende de la implementación. –

+0

El orden solo tiene un efecto en el orden de las invocaciones del constructor. Pero el diseño no está especificado. Hice pruebas hace un tiempo, y GCC pone las bases vacías primero en la memoria, para hacer uso de la optimización de la clase base vacía. –

1

Este es un tema interesante que realmente no es específico de C++. Las cosas se vuelven más complejas también cuando tienes un idioma con despacho múltiple y herencia múltiple (por ejemplo, CLOS).

Las personas ya han notado que hay diferentes formas de abordar el problema. Usted puede encontrar leyendo un poco sobre Meta-Object protocolos (MOP) interesante en este contexto ...

+1

Creo que es mucho más fácil de implementar si el lenguaje no admite punteros "crudos" para referir objetos y ninguna compatibilidad hacia atrás POD. Porque si lo hacen, el lanzamiento de punteros a los objetos es muy complicado. Un lenguaje podría agregar tanta metainformación a la clase de referencia como a la instancia de clase que desee. En C++ esto no se pudo hacer muy fácilmente. – mmmmmmmm

5

Y luego, si hago un reparto:

SecondBase base = (SecondBase *) object_with_base1_and_base2_parents; 

el compilador debe considerar la posibilidad de alterar o no el puntero original. Cosas trucos similares con virtuales.

Con la herencia no virutal esto es menos complicado de lo que parece - en el punto donde se compila el elenco, el compilador sabe la disposición exacta de la clase derivada (después de todo, el compilador hizo el diseño). Por lo general, todo lo que sucede es un desplazamiento fijo (que puede ser cero para una de las clases base) se agrega/resta del puntero de clase derivado.

Con la herencia virutal es tal vez un poco más complejo - puede implicar tomar un desplazamiento de una vblbl (o similar).

El libro de Stan Lippman, "Inside the C++ Object Model" tiene muy buenas descripciones de cómo esto podría funcionar (y a menudo realmente funciona).

0

he realizado sencillo experimento:

class BaseA { int a; }; 
class BaseB { int b; }; 
class Descendant : public BaseA, BaseB {}; 
int main() { 
     Descendant d; 
     BaseB * b = (BaseB*) &d; 
     Descendant *d2 = (Descendant *) b; 
     printf("Descendant: %p, casted BaseB: %p, casted back Descendant: %p\n", &d, b, d2); 
} 

de salida es:

Descendant: 0xbfc0e3e0, casted BaseB: 0xbfc0e3e4, casted back Descendant: 0xbfc0e3e0 

Es bueno darse cuenta de que la fundición estática no siempre significa "cambiar el tipo sin tocar el contenido". (Bueno, cuando los tipos de datos no se ajustan entre sí, también habrá una interferencia en el contenido, pero es diferente la situación de la OMI).