[C++] El principio subyacente del polimorfismo (tabla de funciones virtuales)


prefacio

1. Tabla de funciones virtuales

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

A través de observación y pruebas, encontramos que el objeto b tiene 8 bytes. Además del miembro _b, también hay un __vfptr adicional colocado delante del objeto (tenga en cuenta que algunas plataformas pueden colocarlo al final del objeto, lo que está relacionado con la plataforma) Este puntero en el objeto nosotros Se llama puntero de tabla de funciones virtuales (v significa virtual, f significa función). Una clase que contiene funciones virtuales tiene al menos un puntero de tabla de funciones virtuales, porque
la dirección de la función virtual debe colocarse en la tabla de funciones virtuales
, y la tabla de funciones virtuales también se denomina tabla virtual.

En segundo lugar, la tabla de funciones virtuales en la clase derivada.

1.Principio

class Base
{
    
    
public:
	virtual void Func1()
	{
    
    
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
    
    
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
    
    
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
    
    
public:
	virtual void Func1()
	{
    
    
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
    
    
	Base b;
	Derive d;
	return 0;
}

Insertar descripción de la imagen aquí

1. Las tablas virtuales del objeto de clase base b y el objeto de clase derivada d son diferentes. Aquí encontramos que Func1 ha sido reescrito, por lo que el Derive :: Func1 reescrito se almacena en la tabla virtual de d, por lo que la función virtual es reescrito También llamado cobertura, la cobertura se refiere a la cobertura de funciones virtuales en la tabla virtual. La reescritura se llama sintaxis y la sobrescritura se llama capa principal.
2. Además, Func2 es una función virtual después de ser heredada, por lo que se coloca en la tabla virtual, y Func3 también se hereda, pero no es una función virtual, por lo que no se coloca en la tabla virtual.
3. La tabla de funciones virtuales es esencialmente una matriz de punteros que almacena punteros de funciones virtuales. Generalmente, se coloca un nullptr al final de esta matriz.

Para resumir la generación de tablas virtuales de clases derivadas : a. Primero copie el contenido de la tabla virtual en la clase base a la tabla virtual de la clase derivada. b. Si la clase derivada anula una función virtual en la clase base, use la tabla virtual de la clase derivada. Las funciones virtuales propias cubren las funciones virtuales de la clase base en la tabla virtual C. Las funciones virtuales recién agregadas de la clase derivada se agregan al final de la tabla virtual de la clase derivada en el orden en que se declaran en la clase derivada.

2. Ejemplo:

Insertar descripción de la imagen aquí

  1. Al observar la flecha roja en la figura siguiente, vemos que cuando p apunta al objeto mike, p->BuyTicket encuentra la función virtual Person::BuyTicket en la tabla virtual de mike.
  2. Al observar la flecha azul en la figura siguiente, vemos que cuando p apunta al objeto Johnson, p->BuyTicket encuentra la función virtual Student::BuyTicket en la tabla virtual de Johnson.
  3. De esta forma, diferentes objetos pueden mostrar formas diferentes al realizar el mismo comportamiento.
    Insertar descripción de la imagen aquí

Las llamadas a funciones después de satisfacer el polimorfismo no se determinan en el momento de la compilación, sino que se recuperan del objeto después de la ejecución. Las llamadas a funciones que no satisfacen el polimorfismo se confirman en el momento de la compilación.

3. Ubicación de almacenamiento de funciones virtuales

La tabla virtual almacena punteros de funciones virtuales, no funciones virtuales. Las funciones virtuales son las mismas que las funciones ordinarias y se almacenan en el segmento de código , pero sus punteros se almacenan en la tabla virtual. Además, lo que se almacena en el objeto no es una tabla virtual, sino un puntero de tabla virtual .

Tabla de funciones virtuales compartida del mismo tipo:
dado que las tablas de funciones virtuales de diferentes objetos de la misma clase son iguales, las direcciones de las funciones virtuales que requieren son las mismas, por lo que pueden compartir la misma tabla de funciones virtuales, ahorrando así memoria. espacio. Este diseño permite que cada objeto solo necesite un puntero para apuntar a la tabla de funciones virtuales, sin la necesidad de asignar una tabla de funciones virtuales separada para cada objeto.

Cuarto, la tabla de funciones virtuales en herencia única.

class Person {
    
    
	public:
		virtual void Func1() {
    
    cout << "Person::Func1()" << endl;}
		virtual void Func2() {
    
    cout << "Person::Func2()" << endl;}

		int _a = 0;
	};
class Student : public Person {
    
    
  
private:
	virtual void Func3(){
    
    cout << "Student::Func3()" << endl;}
	protected:
		int _b = 1;
};

	int main() {
    
    
		Person p;
		Student s;
		return 0;
	}

Encontraremos que falta un puntero de función de func3 en la tabla de funciones virtuales.Verificaremos que func3 sí existe en la tabla base virtual de la clase derivada.
Insertar descripción de la imagen aquí
Cada objeto tiene su propio puntero de función virtual , que apunta a la tabla de funciones virtuales correspondiente. Los punteros de función virtual de la clase base y la clase derivada aquí apuntan a la misma dirección porque las funciones virtuales de la clase base no se anulan en la clase derivada (Func1 y Func2). Cuando la clase derivada hereda la clase base, la clase derivada La clase heredará la tabla de funciones virtuales de la clase base y agregará o sobrescribirá sus propias funciones virtuales en su tabla de funciones virtuales. Si la clase derivada no anula la función virtual de la clase base , entonces la dirección de la función virtual correspondiente en la tabla de funciones virtuales de la clase derivada aún apunta a la función virtual en la clase base.
Insertar descripción de la imagen aquí
La tabla de funciones virtuales es esencialmente una matriz de punteros que almacena punteros de funciones virtuales. Se coloca un nullptr al final de esta matriz.

Idea: saque los primeros 4 bytes de los objetos ps y st, que son los punteros de la tabla virtual, y luego use los punteros de la tabla virtual para atravesar la matriz de funciones virtuales. Si el puntero de función de Func3 se puede imprimir, significa que Func3 está en la tabla de funciones virtuales.

typedef void(*FUNC_PTR) ();//FUNC_PTR为函数指针
void PrintVFT(FUNC_PTR* table)//table为函数指针数组
{
    
    
	//虚函数表本质是一个存虚函数
	//指针的指针数组,这个数组最后面放了一个nullpt
	for (size_t i = 0; table[i] != nullptr; i++)
	{
    
       printf("[%d]:%p->", i, table[i]);
		FUNC_PTR f = table[i];
		f();//函数指针指向函数的内存地址,
		//通过调用函数指针,实际上是调用了指向的函数
	}
	printf("\n");
}
	int main() {
    
    
	Person ps;
	Student st;
//1.先取ps的地址,强转成一个int * 的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针(二重函数指针)
	int vft1 = *((int*)&ps);
	PrintVFT((FUNC_PTR*)vft1);
//3.再强转成VFPTR * ,因为虚表就是一个存FUNC_PTR类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVTable进行打印虚表
	int vft2 = *((int*)&st);
	PrintVFT((FUNC_PTR*)vft2);
//5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最
//后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案,再编译就好了。
		return 0;
	}

Insertar descripción de la imagen aquí

Cinco, la tabla de funciones virtuales en herencia múltiple.

Tabla de funciones virtuales de objetos de herencia múltiple

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

Hay múltiples tablas de funciones virtuales en herencia múltiple.

Las funciones virtuales no anuladas de múltiples clases derivadas de herencia se colocan en la tabla de funciones virtuales de la primera parte de la clase base heredada.
Insertar descripción de la imagen aquí

6. Preguntas y respuestas

  1. ¿Puede una función en línea ser una función virtual?
    Respuesta: Sí, pero el compilador ignora el atributo en línea y esta función ya no está en línea porque la función virtual debe colocarse en la tabla virtual 2. ¿Pueden los
    miembros estáticos ser funciones virtuales?
    Respuesta: No, debido a que la función miembro estática no tiene este puntero y no se puede acceder a la tabla de funciones virtuales utilizando el método de llamada de tipo :: función miembro, la función miembro estática no se puede colocar en la tabla de funciones virtuales.
    3. ¿Puede el constructor ser una función virtual?
    Respuesta: No, porque el puntero de la tabla de funciones virtuales en el objeto se inicializa durante la etapa de lista de inicialización del constructor
    4. ¿En qué etapa se genera la tabla de funciones virtuales y dónde existe?
    Respuesta: La tabla de funciones virtuales se genera durante la fase de compilación y generalmente existe en el segmento de código (área constante).
  2. ¿Es más rápido que los objetos accedan a funciones normales o funciones virtuales?
    Respuesta: En primer lugar, si es un objeto normal, es igual de rápido. Si es un objeto puntero o un objeto de referencia, la función ordinaria llamada es más rápida. Debido a que constituye polimorfismo, llamar a una función virtual en tiempo de ejecución requiere buscar en la tabla de funciones virtuales.

Supongo que te gusta

Origin blog.csdn.net/m0_74774759/article/details/132163391
Recomendado
Clasificación