C++ 程序员必经之路 —— 构造、析构、虚析构

/*************************************************/
/*多肽与继承的测试*/
/*************************************************/
#include <iostream>

using namespace std;

class A 
{
public:
	A() { cout<<"Create A"<<endl; }
	virtual ~A() { cout<<"Destroy A"<<endl; }

	virtual void Print() { cout<<"Print A"<<endl; }
};	// 注意这里要有分号,我就犯了这个错误。

class B : public A
{
public:
	B() { cout<<"Create B"<<endl; }
	~B() { cout<<"Destroy B"<<endl; }

	virtual void Print() { cout<<"Print B"<<endl; }
};

int main(int argc, char* argv[])
{
	B *a = new B();	// [1]或者 A *a = new B();
	// a->Print();	// [2]
	delete(a);		// [3]
	a = NULL;		// [4]

	system("pause");
	return 1; 
}


先看上面的代码:

在写下面的内容之前,我觉得首先需要提出:

学习时不要怕犯低级错误,每次犯错,都是一次积累。譬如,在上面代码里,我曾就在定义了class A和class B之后忘记了加分号,导致编译不通过,还找了一会儿才发现这个问题。再如,我在[1]这里创建对象时,曾经写成:

        B b = new A();

        这里一共犯了两个错误,这是JAVA的写法。

        (1).在c++里面,new返回的是指针,所以B b应该是B *b才对。

        (2).第二个错误,编译器会告诉我,不能将对象A转换成对象B,B继承了A,如果通过new A()当然不能创建对象B,只能通过B来创建A,可以这样来理解,因为B包括了A的所有东西,所以通过类A来创建B,必然会导致B的一些属性残缺。正确的应该是: A *a = new B();

 

我想要分享的重点在于以下三点:

1.关于子类和父类构造与虚构调用顺序一句话原则:

        构造调用是由上至下(先父类后子类),析构的调用是由下至上(先子类后父类)。

2.虚析构的意义。

3.遇到不能理解的东西,最好的办法实践。

----------------------------------------------------------------------------------------------------------------------------------------------

运行上面的代码,得到的结果如下:

这验证了第一点:构造的调用顺序总是先调用基类,后调用派生类。对象由底层开始创建,由上层开始析构。所以析构的调用顺序和构造调用顺序是严格逆反的。

如果我们把代码里class A的析构前的 virtual 关键字去掉,再运行一下,结果是这样的:

没看错!没有发生变化。哈哈,别急,接下来,我们把代码里[1]这段代码替换一下:

        B *a = new B();  ----> A *a = new B();

然后再运行一下:

这下不一样了!是的,派生类B的析构函数没有被调用!这样很可能出现:因为没有正确析构而导致内存泄露的问题。

我们再看看百度百科里,对于虚析构函数的介绍:

虚析构函数是为了解决基类指针指向派生类对象,并用基类的指针删除派生类对象。
如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。因为它会为类增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移植性。
所以基本的一条是:无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。
抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。

这里:A *a = new B(); a是指向基类A的指针,再析构时,只调用了基类的析构函数,但没有调用派生类B的析构函数,加入了关键字 virtual 后,才能保证只想基类的指针能正确被删除。

 

好了。这次想要分享的内容差不多就这些了。最后一条是我的切身体会,很多看不懂的东西,拿来实践一番,一下就明白了。希望这些东西能给大家带来帮助!


另外分享一点别人写的关于虚析构的说明,从实现上说明了原因:

虚函数和普通成员函数的区别,是虚函数放在虚函数表中,通过对象的this指针找到该类的虚函数表,然后调用。C++即采用此机制实现多态。如果是普通函数,每个函数的地址是死的。所以用A类的对象调用析构函数时只能调到A的析构。如果是虚函数,则会通过指针找到B的析构函数,而B继承自A,还会调用A的析构函数。

——摘自:http://blog.csdn.net/cffy625/article/details/5225064


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章