Quiero saber qué es una " clase base virtual " y qué significa.
Déjame mostrarte un ejemplo:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
c++
virtual-inheritance
popopome
fuente
fuente
Respuestas:
Las clases base virtuales, usadas en herencia virtual, son una forma de prevenir que aparezcan múltiples "instancias" de una clase dada en una jerarquía de herencia cuando se usa herencia múltiple.
Considere el siguiente escenario:
La jerarquía de clases anterior da como resultado el "diamante temido" que se ve así:
Una instancia de D estará compuesta por B, que incluye A, y C, que también incluye A. Por lo tanto, tiene dos "instancias" (a falta de una mejor expresión) de A.
Cuando tienes este escenario, tienes la posibilidad de ambigüedad. Qué sucede cuando haces esto:
La herencia virtual está ahí para resolver este problema. Cuando especifica virtual cuando hereda sus clases, le está diciendo al compilador que solo desea una única instancia.
Esto significa que solo hay una "instancia" de A incluida en la jerarquía. Por lo tanto
Este es un mini resumen. Para obtener más información, lea esto y esto . Un buen ejemplo también está disponible aquí .
fuente
virtual
, el diseño del objeto se parece al diamante; y si no usamosvirtual
, el diseño del objeto se ve como una estructura de árbol que contiene dosA
sSobre el diseño de la memoria
Como nota al margen, el problema con el Diamante temido es que la clase base está presente varias veces. Entonces, con una herencia regular, crees que tienes:
Pero en el diseño de la memoria, tienes:
Esto explica por qué cuando llama
D::foo()
, tiene un problema de ambigüedad. Pero el verdadero problema viene cuando quieres usar una variable miembro deA
. Por ejemplo, digamos que tenemos:Cuando intente acceder
m_iValue
desdeD
, el compilador protestará, porque en la jerarquía verá dosm_iValue
, no uno. Y si modifica uno, digamos,B::m_iValue
(ese es elA::m_iValue
padre deB
),C::m_iValue
no será modificado (ese es elA::m_iValue
padre deC
).Aquí es donde la herencia virtual es útil, ya que con ella, volverá a un verdadero diseño de diamante, no solo con un
foo()
método, sino también uno y solo unom_iValue
.¿Qué puede salir mal?
Imagina:
A
Tiene alguna característica básica.B
le agrega algún tipo de genial conjunto de datos (por ejemplo)C
agrega algunas características interesantes como un patrón de observación (por ejemplo, encendidom_iValue
).D
hereda deB
yC
, y por lo tanto deA
.Con una herencia normal, modificar
m_iValue
desdeD
es ambiguo y esto debe resolverse. Incluso si es así, hay dosm_iValues
dentroD
, por lo que será mejor que recuerdes eso y actualices los dos al mismo tiempo.Con la herencia virtual, modificar
m_iValue
desdeD
está bien ... Pero ... Digamos que síD
. A través de suC
interfaz, adjuntaste un observador. Y a través de suB
interfaz, actualiza la matriz genial, que tiene el efecto secundario de cambiar directamentem_iValue
...Como el cambio
m_iValue
se realiza directamente (sin usar un método de acceso virtual),C
no se llamará al observador que "escucha" , porque el código que implementa la escucha está dentroC
yB
no lo sabe ...Conclusión
Si tiene un diamante en su jerarquía, significa que tiene un 95% de probabilidad de haber hecho algo mal con dicha jerarquía.
fuente
Explicar la herencia múltiple con bases virtuales requiere un conocimiento del modelo de objetos C ++. Y explicar el tema claramente es mejor hacerlo en un artículo y no en un cuadro de comentarios.
La mejor explicación legible que encontré que resolvió todas mis dudas sobre este tema fue este artículo: http://www.phpcompiler.org/articles/virtualinheritance.html
Realmente no necesitará leer nada más sobre el tema (a menos que sea un escritor compilador) después de leer eso ...
fuente
Creo que estás confundiendo dos cosas muy diferentes. La herencia virtual no es lo mismo que una clase abstracta. La herencia virtual modifica el comportamiento de las llamadas a funciones; a veces resuelve llamadas a funciones que de otro modo serían ambiguas, a veces difiere el manejo de llamadas a funciones a una clase distinta de la que cabría esperar en una herencia no virtual.
fuente
Me gustaría añadir a las amables aclaraciones de OJ.
La herencia virtual no viene sin precio. Al igual que con todas las cosas virtuales, obtienes un éxito de rendimiento. Hay una forma de evitar este éxito de rendimiento que posiblemente sea menos elegante.
En lugar de romper el diamante derivando virtualmente, puede agregar otra capa al diamante, para obtener algo como esto:
Ninguna de las clases hereda virtualmente, todas heredan públicamente. Las clases D21 y D22 ocultarán la función virtual f (), que es ambigua para DD, quizás declarando la función privada. Cada uno de ellos definiría una función de contenedor, f1 () y f2 () respectivamente, cada uno de los cuales llama a la clase local (privado) f (), resolviendo así los conflictos. La clase DD llama a f1 () si quiere D11 :: f () y f2 () si quiere D12 :: f (). Si define los contenedores en línea, probablemente obtendrá aproximadamente cero sobrecarga.
Por supuesto, si puede cambiar D11 y D12, puede hacer el mismo truco dentro de estas clases, pero a menudo ese no es el caso.
fuente
Además de lo que ya se ha dicho sobre la (s) herencia (s) múltiple (s) y virtual (es), hay un artículo muy interesante en el Dr Dobb's Journal: herencia múltiple considerada útil
fuente
Estás siendo un poco confuso. No sé si estás mezclando algunos conceptos.
No tiene una clase base virtual en su OP. Solo tienes una clase base.
Hiciste herencia virtual. Esto generalmente se usa en herencia múltiple para que múltiples clases derivadas usen los miembros de la clase base sin reproducirlos.
No se crea una instancia de una clase base con una función virtual pura. Esto requiere la sintaxis a la que llega Paul. Normalmente se usa para que las clases derivadas deban definir esas funciones.
No quiero explicarte más sobre esto porque no entiendo totalmente lo que estás preguntando.
fuente
Significa que una llamada a una función virtual se reenviará a la clase "correcta".
Preguntas frecuentes sobre C ++ Lite FTW.
En resumen, a menudo se usa en escenarios de herencia múltiple, donde se forma una jerarquía de "diamantes". La herencia virtual romperá la ambigüedad creada en la clase inferior, cuando se llama a la función en esa clase y la función debe resolverse en la clase D1 o D2 por encima de esa clase inferior. Ver el artículo de preguntas frecuentes para obtener un diagrama y detalles.
También se usa en la delegación hermana , una característica poderosa (aunque no para los débiles de corazón). Vea estas preguntas frecuentes.
Consulte también el Artículo 40 en la Eficaz tercera edición de C ++ (43 en la 2da edición).
fuente
Ejemplo de uso ejecutable de herencia de diamantes
Este ejemplo muestra cómo usar una clase base virtual en el escenario típico: para resolver la herencia de diamantes.
fuente
assert(A::aDefault == 0);
desde la función principal me da un error de compilación:aDefault is not a member of A
usando gcc 5.4.0. ¿Qué se supone que debe hacer?Las clases virtuales no son mismo que la herencia virtual. Clases virtuales que no puede crear instancias, la herencia virtual es algo completamente diferente.
Wikipedia lo describe mejor que yo. http://en.wikipedia.org/wiki/Virtual_inheritance
fuente
Herencia regular
Con la herencia típica de 3 niveles sin herencia no virtual de diamante, cuando crea una instancia de un nuevo objeto más derivado, se llama a nuevo y el compilador resuelve el tamaño requerido para el objeto del tipo de clase y lo pasa a nuevo.
nuevo tiene una firma:
Y hace un llamado a
malloc
, devolviendo el puntero vacíoEsto luego se pasa al constructor del objeto más derivado, que llamará inmediatamente al constructor del medio y luego el constructor del medio llamará inmediatamente al constructor base. La base luego almacena un puntero a su tabla virtual al comienzo del objeto y luego sus atributos después. Esto luego regresa al constructor del medio que almacenará su puntero de tabla virtual en la misma ubicación y luego sus atributos después de los atributos que el constructor base habría almacenado. Regresa al constructor más derivado, que almacena un puntero a su tabla virtual en la misma ubicación y luego sus atributos después de los atributos que el constructor central habría almacenado.
Debido a que el puntero de la tabla virtual se sobrescribe, el puntero de la tabla virtual termina siendo siempre una de las clases más derivadas. La virtualidad se propaga hacia la clase más derivada, por lo que si una función es virtual en la clase media, será virtual en la clase más derivada pero no en la clase base. Si convierte polimórficamente una instancia de la clase más derivada en un puntero a la clase base, el compilador no resolverá esto en una llamada indirecta a la tabla virtual y en su lugar llamará a la función directamente
A::function()
. Si una función es virtual para el tipo al que la ha convertido, se resolverá con una llamada a la tabla virtual, que siempre será la de la clase más derivada. Si no es virtual para ese tipo, simplemente llamaráType::function()
y le pasará el puntero del objeto, se convertirá en Tipo.En realidad, cuando digo puntero a su tabla virtual, en realidad siempre hay un desplazamiento de 16 en la tabla virtual.
virtual
no se requiere nuevamente en clases más derivadas si es virtual en una clase menos derivada porque se propaga. Pero se puede usar para mostrar que la función es de hecho una función virtual, sin tener que verificar las clases que hereda las definiciones de tipo.override
es otro protector del compilador que dice que esta función está anulando algo y, si no es así, arroja un error del compilador.= 0
significa que esta es una función abstractafinal
evita que una función virtual se implemente nuevamente en una clase más derivada y se asegurará de que la tabla virtual de la clase más derivada contenga la función final de esa clase.= default
hace explícito en la documentación que el compilador usará la implementación predeterminada= delete
dar un error del compilador si se intenta una llamada a estoHerencia virtual
Considerar
Sin heredar virtualmente la clase de bajo, obtendrá un objeto que se ve así:
En lugar de esto:
Es decir, habrá 2 objetos base.
En la situación de herencia virtual de diamantes anterior, después de llamar a new, llama al constructor más derivado y en ese constructor, llama a los 3 constructores derivados que pasan compensaciones a su tabla de tabla virtual, en lugar de llamar simplemente llamando
DerivedClass1::DerivedClass1()
yDerivedClass2::DerivedClass2()
y luego los que llama tantoBase::Base()
Lo siguiente está todo compilado en modo de depuración -O0, por lo que habrá un ensamblaje redundante
Llama
Base::Base()
con un puntero al desplazamiento de objeto 32. Base almacena un puntero a su tabla virtual en la dirección que recibe y sus miembros después.DerivedDerivedClass::DerivedDerivedClass()
luego llamaDerivedClass1::DerivedClass1()
con un puntero al objeto offset 0 y también pasa la dirección deVTT for DerivedDerivedClass+8
DerivedDerivedClass::DerivedDerivedClass()
luego pasa la dirección del objeto + 16 y la dirección de VTT paraDerivedDerivedClass+24
aDerivedClass2::DerivedClass2()
cuyo montaje es idéntica aDerivedClass1::DerivedClass1()
excepción de la líneamov DWORD PTR [rax+8], 3
que, obviamente, tiene un 4 en vez de 3 parad = 4
.Después de esto, reemplaza los 3 punteros de la tabla virtual en el objeto con punteros para desplazar en
DerivedDerivedClass
vtable a la representación para esa clase.d->VirtualFunction();
:d->DerivedCommonFunction();
:d->DerivedCommonFunction2();
:d->DerivedDerivedCommonFunction();
:((DerivedClass2*)d)->DerivedCommonFunction2();
:((Base*)d)->VirtualFunction();
:fuente