[Análisis en profundidad de C ++] 36. Modelo de objetos de C ++ (a continuación: distribución de memoria en la herencia, tabla de funciones virtuales)

En el último blog, mencionamos que class y struct siguen las mismas reglas de alineación de memoria. Las funciones miembro y las variables de clase en clase se almacenan por separado. El tamaño se utiliza para resolver el tamaño de memoria de las variables miembro. No incluye funciones miembro. Las funciones miembro están en el segmento de código En.

¿Cómo calcular el tamaño de la subclase después de la herencia?

1 modelo de objeto heredado

  • Subclase es una superposición de la clase padre miembro de sub-clase de nuevos miembros get

Inserte la descripción de la imagen aquí
El tamaño de la subclase es la variable miembro de la clase padre seguida de la variable miembro recién agregada de la subclase, y luego se calcula el tamaño.

Experimento de programación: modelo de objeto heredado

// 36-1.cpp
#include<iostream>
using namespace std;
class Demo
{
protected:
    int mi;
    int mj;
public:
    void print()
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << endl;
    }
};
class Derived : public Demo
{
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    void print()
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << ", "
            << "mk = " << mk << endl;
    }
private:
    int mk;
};
struct Test
{
    int mi;
    int mj;
    int mk;
};
int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    Derived d(1, 2, 3);
    d.print();
    Test *p = reinterpret_cast<Test*>(&d);
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    d.print();
    return 0;
}
  • La subclase se obtiene superponiendo a los nuevos miembros de la subclase de los miembros de la clase principal, por lo que Derivado superpone mk sobre la base de los miembros Demo de la clase. Al calcular el tamaño de un objeto de clase, se calcula el tamaño de la variable miembro y la función miembro se guarda en el segmento de código, sin contar el tamaño del objeto. Por lo tanto, el tamaño de la demostración es de 8 bytes y el derivado es de 12 bytes.
  • La distribución de memoria de la clase Derived y struct Test es la misma, puede usar el puntero Test para apuntar al objeto Derived, de modo que este fragmento de memoria se reinterprete y las variables del objeto de clase se puedan modificar.

Compilar y ejecutar:

$ g++ 36-1.cpp -o 36-1
$ ./36-1
sizeof(Demo) = 8
sizeof(Derived) = 12
mi = 1, mj = 2, mk = 3
mi = 10, mj = 20, mk = 30

2 Modelo de objeto polimórfico (función virtual)

2.1 El principio de realización del polimorfismo C ++

  • Al declarar funciones virtuales, el compilador genera una tabla de funciones virtuales ( estructura de datos que almacena las direcciones de las funciones miembro ) en la clase
  • Cuando se define una función virtual en la clase, cada objeto tiene un puntero a la tabla de funciones virtuales
  • El compilador genera y mantiene automáticamente la tabla de funciones virtuales

Como se muestra en la siguiente figura: Al definir una función virtual, además de las variables miembro, el objeto también contendrá un puntero a la tabla de funciones virtuales, que almacena un puntero a la función miembro.

Nota: El puntero de la tabla de funciones virtuales existe en la primera posición de la instancia del objeto.

Inserte la descripción de la imagen aquí

2.2 Tabla de funciones virtuales

Herencia general (sin cobertura de función virtual)
Echemos un vistazo a la tabla de funciones virtuales durante la herencia. Supongamos que existe una relación de herencia como se muestra a continuación:
Inserte la descripción de la imagen aquí
Las funciones miembro son todas funciones virtuales. En esta relación de herencia, la subclase no sobrecarga ninguna clase padre Función. La tabla de funciones virtuales en la clase derivada es la siguiente:
Inserte la descripción de la imagen aquí
podemos ver:

  • Las funciones virtuales se colocan en la tabla en el orden en que se declaran
  • La función virtual de la clase primaria precede a la función virtual de la clase secundaria.

Herencia general (con cobertura de función virtual)
Si hay una función virtual en la subclase que sobrecarga la función virtual de la clase padre, ¿cómo se ve la tabla de funciones virtuales? Tenemos una relación de herencia como la siguiente.
Inserte la descripción de la imagen aquí
Para comparar el efecto después de la herencia, solo una función f () de la clase padre está cubierta en la clase. La tabla de funciones virtuales de la instancia de la clase derivada es la siguiente:
Inserte la descripción de la imagen aquí
puede ver

  • La función superpuesta f () se coloca en la tabla virtual en la función virtual principal original
  • Las funciones que no están cubiertas siguen siendo

El puntero a la tabla de funciones virtuales almacenada en el objeto está en la parte frontal de la memoria, y los siguientes experimentos demuestran la existencia y ubicación del puntero a la tabla de funciones virtuales en el objeto.

Experimento de programación : puntero a la tabla de funciones virtuales en el objeto

Modificamos el código anterior, definimos la función print () en la demostración como una función virtual, agregamos un puntero al frente de la estructura struct Test, demostramos que la memoria de la clase Derived es la misma que la memoria de struct Test.

// 36-1.cpp
#include<iostream>
using namespace std;
class Demo
{
protected:
    int mi;
    int mj;
public:
    virtual void print()		// 虚函数,类对象中将有一个指针,指向虚函数表
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << endl;
    }
};
class Derived : public Demo
{
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    void print()
    {
        cout << "mi = " << mi << ", "
            << "mj = " << mj << ", "
            << "mk = " << mk << endl;
    }
private:
    int mk;
};
struct Test				// 结构体中指针在最前面,和Derived内存布局相同
{
    void* p;
    int mi;
    int mj;
    int mk;
};
int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
    Derived d(1, 2, 3);
    d.print();
    Test *p = reinterpret_cast<Test*>(&d);
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    d.print();
    return 0;
}

Primero calcule el tamaño de la demostración de la clase principal y la clase de subclase Derivada. El objeto contiene un puntero a la tabla de funciones virtuales, el tamaño de la clase es de 8 bytes, por lo que el tamaño de la demostración es 16 y el tamaño derivado es 24.

El puntero a la tabla de funciones virtuales en el objeto de clase está al frente, y el diseño de la memoria es el mismo que el de struct Test. Podemos modificar las variables miembro en la clase a través del puntero de la estructura.

Compilar y ejecutar:

$ g++ 36-1.cpp -o 36-1
$ ./36-1
sizeof(Demo) = 16
sizeof(Derived) = 24
mi = 1, mj = 2, mk = 3
mi = 10, mj = 20, mk = 30

El tamaño de la demostración es de 16 bytes y el tamaño de la derivada es de 24 bytes, lo que demuestra la existencia de punteros en el objeto de clase.

2.3 El proceso de llamar a una función virtual

El proceso de llamar a la función virtual se muestra en la figura a continuación

El objeto de clase busca la tabla de funciones virtuales de acuerdo con el puntero. Hay un puntero de función que apunta a la función virtual en la tabla de funciones virtuales, y la función se puede encontrar de acuerdo con el puntero de función.
Inserte la descripción de la imagen aquí
Si no es una función virtual, el compilador puede determinar directamente la dirección de la función miembro llamada. Por lo tanto, las funciones virtuales son menos eficientes que las funciones miembro ordinarias .

El lenguaje 3 C se da cuenta del polimorfismo

Esta parte es difícil y se utiliza para comprender el proceso de implementación del polimorfismo.

// Polymorphism1.h
#ifndef _POLYYMORPHISM_H_
#define _POLYYMORPHISM_H_
typedef void Demo;
typedef void Derived;

Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);
#endif
// Polymorphism1.c
#include "Polymorphism.h"
#include "malloc.h"

static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);

struct VTable     // 2. 定义虚函数表数据结构
{
    int (*pAdd)(void*, int);   // 3. 虚函数表里面存储什么???
};
struct ClassDemo
{
    struct VTable* vptr;     // 1. 定义虚函数表指针  ==》 虚函数表指针类型???
    int mi;
    int mj;
};
struct ClassDerived
{
    struct ClassDemo d;
    int mk;
};

static struct VTable g_Demo_vtbl = 
{
    Demo_Virtual_Add
};
static struct VTable g_Derived_vtbl = 
{
    Derived_Virtual_Add
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); 
    if( ret != NULL )
    {
        ret->vptr = &g_Demo_vtbl;   // 4. 关联对象和虚函数表
        ret->mi = i;
        ret->mj = j;
    }
    return ret;
}

int Demo_GetI(Demo* pThis)
{
     struct ClassDemo* obj = (struct ClassDemo*)pThis;    
     return obj->mi;
}

int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mj;
}

// 6. 定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mi + obj->mj + value;
}

// 5. 分析具体的虚函数!!!!
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->vptr->pAdd(pThis, value);
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k)
{
    struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));   
    if( ret != NULL )
    {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }   
    return ret;
}

int Derived_GetK(Derived* pThis)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->mk;
}

static int Derived_Virtual_Add(Demo* pThis, int value)
{
    struct ClassDerived* obj = (struct ClassDerived*)pThis; 
    return obj->mk + value;
}

int Derived_Add(Derived* pThis, int value)
{   
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->d.vptr->pAdd(pThis, value);
}
// 36-2.c
#include<iostream>
#include"Polymorphism.h"
using namespace std;
void run(Demo* p, int v)
{
	int r = Demo_Add(p, v);
	printf("r = %d\n", r);
}
int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
    
    run(pb, 3);
    run(pd, 3);
    Demo_Free(pb);
    Demo_Free(pd);
    return 0;
}
$ g++ 36-2.c Polymorphism1.c -o 36-2
$ ./36-2
pb->add(3) = 6
pd->add(3) = 336
r = 6
r = 336

4 Resumen

1. La esencia de la herencia es la superposición de las variables miembro entre padre e hijo
2. El polimorfismo C ++ se realiza a través de la tabla de funciones virtuales (mantenida automáticamente por el compilador)
3. La eficiencia de las funciones virtuales es menor que la de las funciones miembro ordinarias

Publicado 298 artículos originales · elogiado 181 · 100,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/happyjacob/article/details/104411231
Recomendado
Clasificación