1.首先说一下,析构顺序
派生类--> 成员类 --> 基类
2.为什么需要把基类设置为虚析构
因为多态
在c++中,可以使用父类指针指向子类,产生多态行为
代码
#include <iostream>
class TestFather{
public:
~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
};
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
int main() {
TestFather* p = new TestChild();
delete p;
}
//////////////////////////
执行结果
~TestFather()
原因就是,你直接给编译器一个TestFather指针,delete的时候,编译器一看这不就是TestFather,直接调用TestFather析构函数
换成虚析构呢
代码
#include <iostream>
class TestFather{
public:
virtual ~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
};
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
int main() {
TestFather* p = new TestChild();
delete p;
}
///////
执行结果
~TestChild()
~TestFather()
正确了,在delete的时候,编译器会先看TestFather的析构函数是不是虚函数,如果是的话才会产生正常的析构顺序行为,派生类-->成员类-->基类
3. 虚析构函数的本质
虚析构其实也就是虚函数加上析构函数,本质就是会维护一个虚表和指向虚表的指针,在上面代码中TestFather这里面的虚表就只有~TestFather()这一个函数,使用虚函数代表会增加一个指针的内存开销
4. 默认的析构函数
当我们不定义虚构函数的时候,编译器会默认生成一个什么都不做的析构函数,但是注意了默认生成的析构函数就是普通函数不是虚函数!!!因为虚函数会带来额外开销,c++追求的是速度
5. 纯虚构析构函数
就是纯虚函数加上析构函数,一般我们把函数设置纯虚函数都是不想这个类实例化,抽象出来的顶层父类,并且这个纯虚函数不能实现。但是在纯虚析构这里不同
代码
#include <iostream>
class TestFather{
public:
virtual ~TestFather() = 0;
};
TestFather::~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
int main() {
TestFather* p = new TestChild();
delete p;
}
//结果和上面的相同
因为析构函数的调用顺序是派生类 成员类 基类,就算你基类是纯虚函数,编译器还是会产生对他的调用,所以要保证为纯虚析构提供函数体,如果你不做编译器会自动加上。
这里的纯虚实现要在外面实现,不能在类中 = 0之后直接实现,那样直接违反了语法
6. 那么如果父类有纯虚析构函数,子类继承后,怎么定义子类实例呢?
只要子类定义自己的虚函数就可以,不论继承多少层,只要有一个子类定义自己的虚函数,这个子类之后的派生类就都可以定义实例了
7. 关于virtual的隐士传播
在上面的代码中我们基类设置为纯虚函数的时候,这个virtual关键字会被一直继承下去
代码
#include <iostream>
class TestFather{
public:
virtual ~TestFather() = 0;
};
TestFather :: ~TestFather() {
std::cout << "~TestFather()" << std::endl;
}
class TestChild : public TestFather {
public:
~TestChild() {
std::cout << "~TestChild()" << std::endl;
}
};
class TestSun : public TestChild {
public:
~TestSun() {
std::cout << "~TestSun()" << std::endl;
}
};
int main() {
TestFather* p = new TestSun();
delete p;
}
结果:
~TestSun()
~TestChild()
~TestFather()
所以当基类是virtual函数,无论子类的相同函数加或者不加这个关键字都是virtual的,但是为了其他人看代码方便,建议手动把从基类继承的虚函数加上virtual
7. 总结:
明确你的类会不会被继承,当作基类使用,把类的析构函数都设置为虚函数和不设置为虚函数都是不好的
1.如果你的类会被继承,当作基类,那么一定要把基类析构函数设置为虚函数
2.如果你的类不会被继承,单纯的类,那么不需要把析构函数设置为析构函数,因为会浪费空间,多一个虚表指针