C++中使用虚函数的注意相关事项

         在C++中使用虚函数的过程中,进行了小小的总结,大概要注意到以下的几个方面吧。

一、包含虚函数的类指针列表会增大

         让我们先来看一段程序:

#include <iostream>

using namespace std;

class A{
public:
	void aa(){}
	void bb(){}
};

int main()
{
	cout << sizeof(A) << endl;
	return 0;
}

          代码运行结果为:


         程序的运行结果为1,这个结果不出我们的意料。在C++中空类会占一个字节,这是为了让对象的实例能够相互区别。具体来说,空类同样可以被实例化,并且每个实例在内存中都有独一无二的地址,因此,编译器会给空类隐含加上一个字节,这样空类实例化之后就会拥有独一无二的内存地址。如果没有这一个字节的占位,那么空类就无所谓实例化了,因为实例化的过程就是在内存中分配一块地址。

   我们再将上述程序进行如下修改:


   程序的运行结果变为了4。这是因为包含虚函数的类都有一个一维的虚函数表叫作虚表,这个类的每个对象都包含一个指向这个虚表首地址的虚指针。所以导致了包含虚函数类的指针列表增大。

二、虚析构函数

          析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。当派生类的对象从内存中撤销的时候,会先先调用派生类的析构函数然后再调用基类的析构函数。当我们new一个临时对象时,若基类中包含析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生什么样的情况呢?我们来看看下述程序的运行:

#include <iostream>
using namespace std;

class A{
public:
	~A(){
		cout << "based class end;" << endl;
	}
};
class AA :public A{
	~AA(){
		cout << "derived class end;" << endl;
	}
};

int main()
{
	A* a = new AA;
	delete a;
	return 0;
}

         程序运行结果:


        程序结果表明,delete撤销对象时只调用了基类的析构函数,而没有执行派生类的析构函数。对此,我们将基类的析构函数声明为虚函数。代码如下:

#include <iostream>
using namespace std;

class A{
public:
	virtual ~A(){
		cout << "based class end;" << endl;
	}
};
class AA :public A{
public:
	~AA(){
		cout << "derived class end;" << endl;
	}
};

int main()
{
	A* a = new AA;
	delete a;
	return 0;
}

        程序运行结果:


        正如我们所愿,delete在撤销对象时,不仅调用了基类的析构函数而且调用了派生类的析构函数。当基类中的析构函数声明为虚函数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不同。所以,我们要为多态基类声明virtual析构函数。

三、构造函数不能声明为虚函数

         构造函数不能声明为虚函数。如果声明为虚函数,编译器会自动报出:


四、不在析构或者构造过程中调用虚函数

       在析构函数或者是构造函数中,我们绝对不能调用虚函数。即使,我们在构造函数或者析构函数中调用虚函数,也不会下降至派生类中调用函数。我们来看如下代码:

class A{
public:
	int a;
	A(){
		cout << b() << endl;
	}
	virtual int b() const=0;
	virtual int c() const=0;
	virtual ~A(){
		cout << c() << endl;
	}
};
class AA :public A{
public:
	AA() :A(){}
	int b(){
		return 11;
	}
	int c(){
		return 22;
	}
};
int main()
{
	AA a;
	return 0;
}

        上述程序在编译阶段即会报错。AA a;语句执行时,AA构造函数会被先调用,但是A的构造函数会被更早的调用。A构造函数会调用虚函数b(),也正是这里产生编译问题。这个时候,A构造函数调用的A类中的成员函数b(),而不是派生类AA中的b()。因此,基类构造期间,虚函数不会下降到派生类层中。我们可以说:在基类构造期间,virtual函数不是virtual函数。

        要做到绝不在构造和析构过程中调用virtual函数。

猜你喜欢

转载自blog.csdn.net/qq_38697681/article/details/80040328