C++学习笔记-----存在多态调用时,为基类定义虚析构函数
2016-12-20 11:23
375 查看
在C++的继承体系中,构造函数和析构函数的执行顺序是完全相反的。
对于构造函数:从继承体系的最顶层的基类开始,一步步往下构建。即构造顺序是 基类--->>派生类。
对于析构函数:从继承体系的最底层的派生类开始,一步步向上析构。即析构顺序是 派生类-->>基类。
这些对于类的实例化对象来说完全正确,但是如果在程序中存有多态指针,即基类指针指向派生类的时候,需要我们为基类提供一个虚析构函数。
考虑下面的例子。
#include "stdafx.h"
#include <iostream>
class Base
{
public:
Base() //构造函数
{
std::cout << "Base Constructor" << std::endl;
}
~Base() //non_virtual析构函数
{
std::cout << "Base Destructor" << std::endl;
}
virtual void func() //virtual成员函数
{
std::cout << "Base" << std::endl;
}
protected:
std::string Bmsg;
};
class Derive : public Base
{
public:
Derive()
{
std::cout << "Derive Constructor" << std::endl;
}
~Derive()
{
std::cout << "Derive Destructor" << std::endl;
}
void func()
{
std::cout << "Derive" << std::endl;
}
private:
std::string Dmsg;
};
int main()
{
Base b;
std::cout << std::endl;
Derive d;
std::cout << std::endl;
Base * pb = new Derive;
std::cout << std::endl;
b.func();
std::cout << std::endl;
d.func();
std::cout << std::endl;
pb->func();
std::cout << std::endl;
delete pb;
return 0;
}
让我们一步步解释输出结果。
Base b;实例化一个基类的对象,调用基类的构造函数,输出“Base Constructor”。
Derive d;实例化一个派生类的对象,会向上调用构造函数。先调用基类的构造函数输出"Base Constructor“,再调用自身的构造函数输出"Derive Constructor"。
Base * pb = new Derive;定义了一个基类指针指向派生类,同样会先调用基类构造函数,再调用自身构造函数。这条语句定义了一个多态指针。
b.func();通过基类的实例化对象调用自身的virtual成员函数func,输出”Base“
delete pb;释放从堆栈申请的内存,会先对指针所指对象进行析构,然后调用operator delete函数来释放申请的内存空间。
我们发现,这条语句没有调用派生类的析构函数,仅仅调用了基类的。这会导致一个问题,就是pb所指的这一个派生类对象的基类部分被析构了,而其自身独立于基类之外的部分没有被析构。就本例而言,基类的string类型变量Bmsg被析构了,而自身的string类型变量Dmsg没有析构。结果会产生一个不完整的对象。
如前面所述,我们需要为基类提供一个虚析构函数,就像这样
virtual ~Base()
{
std::cout << "Base Destructor" << std::endl;
}
其他部分保持不变,我们来看一下输出结果
在执行delete pb;语句时先调用了派生类的析构函数,而后又调用了基类的析构函数。整个对象被完全析构了。内存也完全被释放了。
综上,对于是否应该为基类提供一个虚析构函数,我们可以这样理解:
如果程序中存在多态指针,即存在基类指针指向派生类对象的,那么请为基类提供一个虚析构函数。
其他的情况不需要将基类的析构函数设置为虚函数。
对于构造函数:从继承体系的最顶层的基类开始,一步步往下构建。即构造顺序是 基类--->>派生类。
对于析构函数:从继承体系的最底层的派生类开始,一步步向上析构。即析构顺序是 派生类-->>基类。
这些对于类的实例化对象来说完全正确,但是如果在程序中存有多态指针,即基类指针指向派生类的时候,需要我们为基类提供一个虚析构函数。
考虑下面的例子。
#include "stdafx.h"
#include <iostream>
class Base
{
public:
Base() //构造函数
{
std::cout << "Base Constructor" << std::endl;
}
~Base() //non_virtual析构函数
{
std::cout << "Base Destructor" << std::endl;
}
virtual void func() //virtual成员函数
{
std::cout << "Base" << std::endl;
}
protected:
std::string Bmsg;
};
class Derive : public Base
{
public:
Derive()
{
std::cout << "Derive Constructor" << std::endl;
}
~Derive()
{
std::cout << "Derive Destructor" << std::endl;
}
void func()
{
std::cout << "Derive" << std::endl;
}
private:
std::string Dmsg;
};
int main()
{
Base b;
std::cout << std::endl;
Derive d;
std::cout << std::endl;
Base * pb = new Derive;
std::cout << std::endl;
b.func();
std::cout << std::endl;
d.func();
std::cout << std::endl;
pb->func();
std::cout << std::endl;
delete pb;
return 0;
}
让我们一步步解释输出结果。
Base b;实例化一个基类的对象,调用基类的构造函数,输出“Base Constructor”。
Derive d;实例化一个派生类的对象,会向上调用构造函数。先调用基类的构造函数输出"Base Constructor“,再调用自身的构造函数输出"Derive Constructor"。
Base * pb = new Derive;定义了一个基类指针指向派生类,同样会先调用基类构造函数,再调用自身构造函数。这条语句定义了一个多态指针。
b.func();通过基类的实例化对象调用自身的virtual成员函数func,输出”Base“
d.func();通过派生类的实例化对象调用自身的virtual成员函数func, 输出”Derive"
pb->func();通过指向派生类的基类指针调用虚函数func,因为多态指针是动态绑定的,所以此时调用的是基类的func还是派生类的func需要在运行时判断pb指针此时此刻指向的是基类成员还是派生类成员。本例中指向派生类成员,所以会输出"Derive"。
delete pb;释放从堆栈申请的内存,会先对指针所指对象进行析构,然后调用operator delete函数来释放申请的内存空间。
我们发现,这条语句没有调用派生类的析构函数,仅仅调用了基类的。这会导致一个问题,就是pb所指的这一个派生类对象的基类部分被析构了,而其自身独立于基类之外的部分没有被析构。就本例而言,基类的string类型变量Bmsg被析构了,而自身的string类型变量Dmsg没有析构。结果会产生一个不完整的对象。
如前面所述,我们需要为基类提供一个虚析构函数,就像这样
virtual ~Base()
{
std::cout << "Base Destructor" << std::endl;
}
其他部分保持不变,我们来看一下输出结果
在执行delete pb;语句时先调用了派生类的析构函数,而后又调用了基类的析构函数。整个对象被完全析构了。内存也完全被释放了。
综上,对于是否应该为基类提供一个虚析构函数,我们可以这样理解:
如果程序中存在多态指针,即存在基类指针指向派生类对象的,那么请为基类提供一个虚析构函数。
其他的情况不需要将基类的析构函数设置为虚函数。
相关文章推荐
- C++ Primer 学习笔记_65_面向对象编程 -概述、定义基类跟派生类
- C++学习笔记(7)——多基类继承的构造函数的调用
- C++学习笔记(5)——基类、派生类的构造函数、析构函数的调用顺序
- C++学习笔记(13)——利用对象、引用、指针调用虚函数
- C++ prime学习笔记之类定义
- 继承和多态和虚函数——C++学习笔记二
- c风格回调函数 vs c++风格虚基类,关于接口定义和调用的对比
- C++学习笔记之继承层次中的函数调用。
- C++学习笔记(10)——虚基类的作用
- C++学习笔记(四):虚析构函数
- C++虚函数和多态学习笔记
- c++学习笔记之函数的调用和返回
- c++基础学习 - 宏的定义和调用
- 派生类到基类的转换(c++学习笔记)
- C/C++沉思-----多态时一定要将父类(基类)的析构函数定义为虚函数
- 派生类到基类的转换(c++学习笔记)
- C++学习笔记(8)——继承中的二义性问题和虚基类
- C++学习笔记:通过Virtual函数和Overload实现多态
- [疑问]C/C++中为什么在类外利用多态基类指向派生类指针可以调用类的私有成员函数?
- Objective-C 学习笔记之基本语法(1/2): 类的定义、继承、多态与接口等