1、为什么构造函数不可以是虚函数
①从存储空间角度
虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。
②从使用角度
虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
2、为什么析构函数可以是虚函数
编译器总是根据类型来调用类成员函数。一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。
所以建议的方式是将析构函数声明为虚函数。一个函数一旦声明为虚函数,那么不管你是否加上virtual 修饰符,它在所有派生类中都成为虚函数。但是由于理解明确起见,建议的方式还是加上virtual 修饰符。
C++不把虚析构函数直接作为默认值的原因是虚函数表的开销以及和C语言的类型的兼容性。有虚函数的对象总是在开始的位置包含一个隐含的虚函数表指针成员。
示例:
举一个示例一看就明白了。
#include <iostream>
using namespace std;
class Box
{
public:
const char *name;
int age;
float score;
Box() {
cout << "调用构造函数Box!" << endl;
}
~Box() {
cout << "调用析构函数Box!" << endl;
}
void say() {
cout << name << "的年龄是" << age << ",成绩是" << score << endl;
}
};
class BigBox : public Box
{
public:
const char *name;
int age;
float score;
BigBox() {
cout << "调用构造函数BigBox!" << endl;
}
~BigBox() {
cout << "调用析构函数BigBox!" << endl;
}
void say() {
cout << name << "的年龄是" << age << ",成绩是" << score << endl;
}
};
int main()
{
// new运算符,在堆上新建对象,调用构造函数,并返回该对象的指针
Box* box = new BigBox;
box->name = "ss";
box->age = 18;
box->score = 100;
box->say();
// delete运算符,释放堆上的对象,调用对象的析构函数
delete box;
getchar();
return 0;
}
输出:
BigBox继承Box,Box*基类指针指向了派生类对象,但调用delete box时,却是调用的基类的析构函数!这时,由于没有重载析构函数,产生了资源泄漏。
只需要把基类的析构函数变成虚函数,就可以调用派生类的析构函数了。
#include <iostream>
using namespace std;
class Box
{
public:
const char *name;
int age;
float score;
Box() {
cout << "调用构造函数Box!" << endl;
}
virtual ~Box() {
cout << "调用析构函数Box!" << endl;
}
void say() {
cout << name << "的年龄是" << age << ",成绩是" << score << endl;
}
};
class BigBox : public Box
{
public:
const char *name;
int age;
float score;
BigBox() {
cout << "调用构造函数BigBox!" << endl;
}
~BigBox() {
cout << "调用析构函数BigBox!" << endl;
}
void say() {
cout << name << "的年龄是" << age << ",成绩是" << score << endl;
}
};
int main()
{
// new运算符,在堆上新建对象,调用构造函数,并返回该对象的指针
Box* box = new BigBox;
box->name = "ss";
box->age = 18;
box->score = 100;
box->say();
// delete运算符,释放堆上的对象,调用对象的析构函数
delete box;
getchar();
return 0;
}
输出:
这时,派生类的析构函数也被调用了,就不会产生资源泄漏了。
还有,更方便的实现方式,C++11提供了shared_ptr智能指针,可以把对象交给shared_ptr托管,在对象过期时,内存将会自动被释放。
#include <iostream>
#include <memory>
using namespace std;
class Box
{
public:
const char *name;
int age;
float score;
Box() {
cout << "调用构造函数Box!" << endl;
}
~Box() {
cout << "调用析构函数Box!" << endl;
}
void say() {
cout << name << "的年龄是" << age << ",成绩是" << score << endl;
}
};
class BigBox : public Box
{
public:
const char *name;
int age;
float score;
BigBox() {
cout << "调用构造函数BigBox!" << endl;
}
~BigBox() {
cout << "调用析构函数BigBox!" << endl;
}
void say() {
cout << name << "的年龄是" << age << ",成绩是" << score << endl;
}
};
int main()
{
shared_ptr<Box> box = make_shared<BigBox>();
box->name = "ss";
box->age = 18;
box->score = 100;
box->say();
getchar();
return 0;
}
输出: