Una breve discusión sobre los errores comunes en el sistema de herencia en C++.

Una breve discusión sobre los errores comunes en el sistema de herencia en C++.

En la actualidad, según mi experiencia laboral, hay dos cosas en las que siempre cometo errores en el sistema de herencia en C++:

  1. No establezca métodos que deban anularse en la clase base como métodos virtuales.
  2. No configure el destructor en la clase base como un método virtual.

En cuanto al primer punto, ¿qué sucede si el método que debe anularse en la clase base no está configurado como método virtual? La consecuencia es que la subclase hereda la clase. Si la subclase quiere anular el método de la clase principal, no tendrá éxito. Cuando se produce el enlace dinámico, la clase principal aún llama al método de la clase principal y no llama al método. del método de la subclase. Echemos un vistazo a la columna real:

#include<iostream>
class Father {
    
    
public:
	Father(){
    
    }
	~Father(){
    
    }
	void Show()
	{
    
    
		std::cout << "I'm your Father" << std::endl;
	}
};

class Son : public Father {
    
    
public:
	Son() {
    
    }
	~Son(){
    
    }
	void Show()
	{
    
    
		std::cout << "I'm your Son" << std::endl;
	}
};

int main()
{
    
    
	Father* father = new Son;
	father->Show();
	system("pause");
}

Después de leer el código, primero pensemos en qué se genera exactamente: ¿llama al show del padre () o al show del hijo ()?
Salida:
Soy tu padre,
por favor presiona cualquier tecla para continuar...
Como era de esperar, el padre llamó a su propio método con una cara lívida. Mi hijo nunca recibe alimento, pobrecito.
Es hora de hacer cambios, ahora cambiemos el método show () de la clase principal a un método virtual (virtual void Show();) y veamos el efecto.

#include<iostream>

class Father {
    
    
public:
	virtual void Show()
	{
    
    
		std::cout << "I'm your Father" << std::endl;
	}
};

class Son : public Father {
    
    
public:
	void Show()
	{
    
    
		std::cout << "I'm your Son" << std::endl;
	}
};

int main()
{
    
    
	... // 与上面一致
}

Salida modificada:
Soy tu hijo.
Presiona cualquier tecla para continuar...
Este tipo de problema de herencia es bastante común. El método que necesitamos cubrir debe modificarse con la clave virtual; de lo contrario, incluso si se produce un enlace dinámico, no se llamará al método de la subclase, lo cual está lejos de lo que esperábamos, o incluso un error. De hecho, existe una buena manera de evitar este tipo de error: simplemente agregue anulación al método de la subclase. Esta palabra clave detecta automáticamente si el método de la subclase puede anular el método de la clase principal, incluido: si el método de la clase principal es un método virtual y si el método de la subclase es coherente con el método de la clase principal. Entonces, según la especificación, se escribe como:

#include<iostream>
class Father {
    
    
public:
	virtual void Show()
	... // 与上面一致
};

class Son : public Father {
    
    
public:
	virtual void Show() override
	...
};

int main()
{
    
    
	... 
}

Como dice el refrán, no hay regla sin reglas , seguir las reglas siempre reducirá las posibilidades de cometer errores .

El segundo punto propenso a errores, que también es una pregunta favorita en las salas de entrevistas, es que en el sistema de herencia, el método destructor de la clase base debe establecerse en un método virtual. Una palabra más, si a la sala de entrevistas siempre le gusta hacer esta pregunta, entonces podemos simplemente hablar sobre los puntos principales:
configurar el destructor de clase base como un método virtual es para evitar pérdidas de memoria, ¿cómo se dice? Si nuestra clase derivada solicita algo de memoria y la libera en el destructor, entonces el destructor de la subclase no será llamado cuando se elimine el puntero de la clase base. ¿Por qué no? ¿No acabamos de hacer un experimento? Si la subclase anula el método de la clase principal, si no es un método virtual, no se producirá ningún enlace dinámico, por lo que, naturalmente, solo se llamará al método de la clase principal, nunca se llamará al método de la subclase y no se llamarán los métodos. ¿Cómo es posible liberar la memoria para que se produzca una pérdida de memoria? Por supuesto, la subclase en sí no se publica y se convierte en un puntero salvaje. Causa pérdida de memoria.
La gente no habla mucho, vamos, solo ve al código, el código es el destino del alma.

#include<iostream>

class Base {
    
    
public:
	Base()
	{
    
    

	}
	//  故意不设置为虚方法
	~Base()
	{
    
    
		std::cout << " Base 析构完成..." << std::endl;
	}
	virtual void DoSomething() = 0;
};

class Derive : public Base {
    
    
public:
	Derive() :m_data(nullptr)
	{
    
    
		m_data = new char[m_lenth];
	}

	~Derive()
	{
    
    
		delete[] m_data;
		std::cout << " Derive 析构完成..." << std::endl;
	}
	virtual void DoSomething() override
	{
    
    
		// ...
	}
private:
	// 既然是狠人,我们就new 500M内存
	// 这儿为什么不直接写524288000呢? 其实多个嘴,
	// c++有编译和运行阶段,像这种常量,编译阶段能计算出来的,
	// 编译器早就优化好了,根本就等不到运行时才算,所以啊,
	// 常量我们尽量写的直白点,像直接写524288000 这种魔鬼数字,鬼懂哦
	const unsigned int m_lenth = 500 * 1024 * 1024;
	char* m_data;
};

int main()
{
    
    
	Base* base = new Derive;
	base->DoSomething();
	delete base;
	while (true)
	{
    
    
		// 方便我们在任务管理器里看看内存到底有没有被回收
	}
	system("pause");
	return 0;
}

Eche un vistazo al resultado y al estado de la memoria del Administrador de tareas.
Salida:
Destructor base completado...
acabo de imprimir esta oración, pobre, el destructor de la subclase no se ejecutó. Es difícil.
Administrador de tareas:
Memoria
mire los 500 M de memoria que no se han liberado. Si dicho código se ejecuta en el servidor, los pocos cientos de gigabytes de memoria del servidor no serán suficientes y colapsará después de unos pocos ciclos.
Cambiemos el código y cambiemos el destructor de la clase principal a un método virtual.

#include<iostream>

class Base {
    
    
public:
	...
	virtual ~Base()
	{
    
    
		std::cout << " Base 析构完成..." << std::endl;
	}
	virtual void DoSomething() = 0;
};

class Derive : public Base {
    
    
public:
	... // 和上保持一致
private:
	...
};

int main()
{
    
    
	Base* base = new Derive;
	base->DoSomething();
	delete base;
	while (true)
	{
    
    
		// 方便我们在任务管理器里看看内存到底有没有被回收
	}
	system("pause");
	return 0;
}

Echemos un vistazo al efecto de ejecución:
Salida:
Derivación del destructor completada...
Destructor base completado...
Administrador de tareas:
Memoria liberada correctamente
En la salida, podemos ver que la clase principal ha llamado al destructor de la subclase y el administrador de tareas muestra que la memoria ha sido liberada. Desde una perspectiva práctica, es necesario configurar el destructor de la clase principal como un método virtual porque implica pérdidas de memoria. Está bien cuando se usa localmente. Si la memoria se agota y se reinicia, estará bien. Si el servidor es así, no funcionará. En ese momento, no afectará a una persona, sino a miles de usuarios. No entraré en la discusión principal a este respecto. Le sugiero que lea más:
"Primer C++ 5ta edición" Capítulo 15
"C++ efectivo" 07. Destructor virtual de declaración de clase base polimórfica.
Finalmente, diré algunas palabras. Si Si queremos heredar una clase de una biblioteca de terceros, debemos tener cuidado de ver si la clase base tiene un destructor virtual. Por ejemplo, es imposible para mí heredar un std::string, esto es simplemente una tontería. Ninguno de ellos tiene funciones virtuales ficticias. Entonces, hay que tener cuidado con todo.

Supongo que te gusta

Origin blog.csdn.net/qq_33944628/article/details/120444177
Recomendado
Clasificación