基类与派生类,父类指针指向子类对象

Posted guhowo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基类与派生类,父类指针指向子类对象相关的知识,希望对你有一定的参考价值。

先看一段代码:

 1 #include<iostream>
 2 
 3 using namespace std;
 4 
 5 class Base{
 6 public:
 7     Base() { cout<<"Base Creted"<<endl; }
 8     ~Base() { cout<<"Base Destroyed"<<endl; }
 9 };
10 
11 class Derived: public Base {
12 public:
13     Derived() { cout<<"Derived Created"<<endl; }
14     ~Derived() { cout<<"Derived Destroyed"<<endl; }
15 };
16 
17 int main()
18 {
19     Base *pB = new Derived();
20     delete pB;
21     pB = NULL;
22     return 0;
23 }

运行结果如下,情理之中,意料之内:

C++创建对象的时候先创建基类部分,然后创建派生部分。析构的时候要反过来了,先释放子类部分,然后在释放父类部分。但是这里只释放了父类部分,没有释放派生类的部分。为什么呢?

原因很明确:因为之类pB是基类指针,虽然指向的是派生类,只能调用自己的函数,我们是无法通过基类指针调用到子类的成员函数的(除非采用virtual,也就是迟绑定技术)。

虚函数

为了不造成内存泄漏,我们将上面代码改成:

1 class Base{
2 public:
3     Base() { cout<<"Base Creted"<<endl; }
4     virtual ~Base() { cout<<"Base Destroyed"<<endl; }   //virtual关键字很关键哦
5 };

运行结果正确了:

和之前版本相比,基类的析构函数多了一个 virtual 关键字。这样就使得父类类型的指针可以调用子类的成员函数。虚拟函数就是为了对“如果你以一个基础类指针指向一个衍生类对象,那么通过该指针,你只能访问基础类定义的成员函数”这条规则反其道而行之的设计。如果你打算将某个类作为基类,那么一定要定义一个虚析构函数。

虚函数通过动态绑定技术实现了C++的运行时的多态性。让我们可以通过基类的指针或者引用调用派生类的方法。C++中还有一个多态性是编译时的多态,通过模版实现。

《Effective C++》条款 07 p40:为多态基类声明virtual析构函数。

《Effective C++》条款 07 p44:

  • 带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数
  • Classes的设计目的如果不是作为base classes 使用,或不是为了具备多态性,就不该声明virtual析构函数。

动态绑定的是怎么实现的?

1、为每个含义虚函数的类创建一个虚函数表VTable,存到常量区,依次存放虚函数的地址。对于每个派生类来说,如果没有重写基类的虚函数,那么派生类的虚函数表中的函数地址还是基类的那个虚函数地址。

2、为每个含有虚函数的对象创建一个指向VTable的指针VPtr,所以说同类对象的VPtr是一样的。

3、当基类指针指向派生类时,放生了强制转换,基类的指针指向了派生类的VPtr,这样当pBase->func()时,就可以调用派生类的func()了。

4、没有虚函数的类也就没有VTable表了,或者这个表为空。这样基类指针自然调用不到派生类的函数了。

 

思考:

为何要让父类指针指向派生类对象?

我觉得是为了实现C++的多态性。比如简单工厂模式,一开始我们并不知道需要生产哪种产品,也就不知道返回的是什么类型的产品,此时只能用基类指针去指向返回值。除此之外还没想到其他的使用场景。

以上是关于基类与派生类,父类指针指向子类对象的主要内容,如果未能解决你的问题,请参考以下文章

将基类指针强制转换为子类指针后的疑惑

总结C++基类与派生类的赋值兼容规则

c++ 虚函数 如何调父类 而非子类

基类与派生类的指针和成员函数调用原理

赋值兼容原则

在基类中存储指向派生类函数的指针