A brief discussion on the common mistakes in the inheritance system in C++

A brief discussion on the common mistakes in the inheritance system in C++

At present, based on my work experience, there are two things that I always make mistakes in the inheritance system in C++:

  1. Do not set methods that need to be overridden in the base class as virtual methods.
  2. Do not set the destructor in the base class as a virtual method.

As for the first point, what happens if the method that needs to be overridden in the base class is not set as a virtual method? The consequence is that the subclass inherits the class. If the subclass wants to override the method of the parent class, it will not succeed. When dynamic binding occurs, the parent class still calls the method of the parent class and does not call the method of the subclass. method. Let’s take a look at the actual column:

#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");
}

After reading the code, let's first think about what exactly is output. Is it calling Father's show() or Son's Show()?
Output:
I'm your Father,
please press any key to continue...
As expected, the father called his own method with a livid face. My son never gets any nourishment, poor baby.
It's time to make changes. Now let's change the show() method of the parent class into a virtual method (virtual void Show();) and see the effect.

#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()
{
    
    
	... // 与上面一致
}

Changed output:
I'm your Son
Please press any key to continue...
This kind of inheritance problem is quite common. The method we need to cover must be modified with the virtual key, otherwise even if dynamic binding occurs, the method of the subclass will not be called, which is far from what we expected, or even an error. In fact, there is a good way to prevent this kind of error, just add override to the method of the subclass. This keyword automatically detects whether the method of the subclass can override the method of the parent class, including: whether the method of the parent class is a virtual method, and whether the method of the subclass is consistent with the method of the parent class. So according to the specification, it is written as:

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

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

int main()
{
    
    
	... 
}

As the saying goes, there is no rule without rules . Following the rules will always reduce the chance of making mistakes .

The second error-prone point, which is also a favorite question in interview rooms, is that in the inheritance system, the destructor method of the base class must be set to a virtual method. One more word, if the interview room always likes to ask this question, then we can just talk about the main points:
setting the base class destructor as a virtual method is to prevent memory leaks. How do you say it? If our derived class applies for some memory and releases it in the destructor, then the destructor of the subclass will not be called when the base class pointer is deleted. Why not? Didn't we just do an experiment? If the subclass overrides the method of the parent class, if it is not a virtual method, no dynamic binding will occur, so naturally only the parent class's own method will be called. The method of the subclass will never be called, and the methods will not be called. How is it possible to release the memory, so a memory leak occurs. Of course, the subclass itself is not released and becomes a wild pointer. Cause memory leak.
People don't talk much, come on, just go to the code, the code is the destination of the soul.

#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;
}

Take a look at the output and the memory status of Task Manager.
Output:
Base destructor completed...
just printed this sentence, poor, the destructor of the subclass did not run. It’s difficult.
Task Manager:
Memory
Look at the 500M of memory that has not been released. If such code is executed on the server, the server's mere few hundred gigabytes of memory will not be enough. It will crash after a few cycles.
Let’s change the code and change the destructor of the parent class into a virtual method.

#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;
}

Let’s take a look at the running effect:
Output:
Derive destructor completed...
Base destructor completed...
Task manager:
Correctly released memory
From the output, we can see that the parent class has called the destructor of the subclass, and the task manager shows that the memory has been released. From a practical perspective, it is necessary to set the destructor of the parent class to a virtual method because it involves memory leaks. It's fine when used locally. If the memory is exhausted and restarted, it will be fine. If the server is like this, it won't work. At that time, it will not affect one person, but thousands of users. I will not go into the principle discussion in this regard. I suggest you read more:
"Primer C++ 5th Edition" Chapter 15
"Effective C++" 07. Polymorphic base class declaration virtual destructor.
Finally, I will say a few words. If we want to inherit a class from a third-party library, we must be careful to see if the base class has a virtual destructor. For example, it is impossible for me to inherit a std::string. This is simply nonsense. None of them have virtual fictitious functions. So, you have to be careful about everything.

Guess you like

Origin blog.csdn.net/qq_33944628/article/details/120444177