Estoy estudiando la herencia de C ++ y escribí este código:

// 08 Diamond Inheritance 02.cpp : Defines the entry point for the console application.
//   I'm using Microsoft Visual Studio 2015

#include <iostream>

using namespace std;

class SuperVirtual
{
public:
    int sv;
    SuperVirtual(int p1 = 11) : sv(p1)
    {
        cout << "\n SuperVirtual ctor for &" << this;
    }

    virtual void methodSuperVirtual_01()
    {
        cout << "\n Inside SuperVirtual::methodSuperVirtual_01(): sv = " << ++sv;
    }

    virtual void methodSuperVirtual_02()
    {
        cout << "\n Inside SuperVirtual::methodSuperVirtual_02(): sv = " << ++sv;
    }
};

//---------------------------------- 
class DerivedSV_01 : public SuperVirtual
{
public:
    int dsv;
    DerivedSV_01(int p1 = 21) : dsv(p1)
    {
        cout << "\n DerivedSV_01 ctor for &" << this;
    }
    virtual void methodSuperVirtual_01() override
    {
        cout << "\n Inside DerivedSV_01::methodSuperVirtual_01()";
    }
    //----------------------------------------------------
    virtual void onlyForDerivedSV_01()
    {
        cout << "\n Inside DerivedSV_01::onlyForDerivedSV_01()";
    }
};

int main()
{
    SuperVirtual sv1(1);
    sv1.methodSuperVirtual_01();

    DerivedSV_01 dsv_01;
    dsv_01.methodSuperVirtual_01();
    dsv_01.onlyForDerivedSV_01();

    return 0;
}

Había leído eso:

Siempre que una clase contenga funciones virtuales o anule funciones virtuales de una clase primaria, el compilador construye una vtable para esa clase. El vtable contiene punteros de función que apuntan a las funciones virtuales en esa clase. Solo puede haber una vtable por clase, y todos los objetos de la misma clase compartirán la misma vtable.

En este punto, supuse que la tabla virtual de la clase SuperVirtual contiene 2 elementos, mientras que la tabla virtual para la clase DerivedSV_01 contiene 3 elementos: 2 para los métodos heredados (y eventualmente superados) + 1 para el nuevo método virtual onlyForDerivedSV_01().

Sin embargo, al inspeccionar los objetos creados en main() con el depurador de Visual Studio, descubrí que:

  1. La tabla virtual, por ejemplo, la clase SuperVirtual contiene 2 elementos (¡OK!)

  2. La tabla virtual, por ejemplo, la clase DerivedSV_01 contiene 2 elementos ( ¿Por qué? )

Para completar la información, proporciono la siguiente captura de pantalla:

image

0
openyourmind 10 feb. 2020 a las 14:21

2 respuestas

La mejor respuesta

Puedo reproducir esto con exactamente su código en VS2019 al compilar en modo de depuración (optimización del compilador desactivada). Estoy bastante seguro de que esto es solo un problema de visualización en la ventana del depurador.

locals window

Tenga en cuenta que la matriz __vfptr solo se muestra debajo de SuperVirtual, no directamente debajo de dsv_01, por lo que, por supuesto, el depurador solo conoce las funciones en una tabla SuperVirtual.

Pero si mira la columna Value para __vfptr, notará que en SuperVirtual::'vftable'[3] y DerivedSV_01::'vftable[4]' (ambos marcados en verde) se dan diferentes tamaños de matriz.

Echemos un vistazo a la memoria de las tablas v (las direcciones de las tablas v se pueden ver al comienzo de la columna Value para las entradas __vfptr y están marcadas en naranja y rojo). ventana de memoria ventana de memoria

Notarás que SuperVirtual::'vftable'[3] tiene dos punteros de función (marcados en marrón) y un nullptr. DerivedSV_01::'vftable[4]' tiene tres punteros (marcados morado y azul) y un nullptr. La ventana del depurador nos dice cuáles son las dos primeras entradas (también marcadas en púrpura), pero eche un vistazo a la tercera entrada en la ventana de observación (marcada en azul).

watch window

El depurador dice que la tercera entrada es DerivedSV_01::onlyForDerivedSV_01. Eso coincide totalmente con tus (y mis) expectativas.

2
Werner Henze 11 feb. 2020 a las 20:21

La respuesta es simple "COMO SI" entró en vigor la regla.

El compilador descubre que onlyForDerivedSV_01 nunca se usa de forma polimórfica (ya que no hay subclases de DerivedSV_01), por lo que se ha convertido al método normal.

Como resultado, solo tiene dos métodos virtuales heredados de la clase base.

De hecho, con la optimización completa, el compilador habilitado debería poder eliminar todas las llamadas virtuales para este código. Aquí puede ver todos los métodos se colocan directamente en la función main y solo se realizan llamadas virtuales en std::cout.

1
Marek R 10 feb. 2020 a las 11:45