一、父类指针指向子类对象
首先,来看一段代码:
//用父类指针指向子类对象 class A { public: A() { printf("A "); } ~A() { printf(" ~A "); } // 这里不管写不写virtual,删除B对象的时候,都会被执行。因为这个例子是B*指针指向B对象,不是A*指针指向B对象。 }; class B : public A { public: B() { printf("B "); } ~B() { printf("~B "); } }; int main(int argc, char* argv[]) { B* b = new B; delete b; return 0; } int main(int argc, char* argv[]) { B b; // 虽然正确,但其实是非正常情况,因为你防不住程序员定义A* a = new B(); 这样就错了。 return 0; } 执行结果: A B ~B ~A
结论:删除子类指针,无论如何会自动调用祖先类的析构函数(即使祖先类的习惯函数不是虚拟的),虽然这是非正常情况,但还是记一下。
第二段代码:
class A { public: A() { printf("A "); } virtual ~A() { printf(" ~A "); } // 增加了虚拟关键字 }; class B : public A { public: B() { printf("B "); } ~B() { printf("~B "); } }; int main(int argc, char* argv[]) { A* a = new B; delete a; return 0; } 执行结果: A B ~B ~A
结论:正常情况下,子类祖先类的析构函数都是虚拟的,这样删除祖先类指针、子类对象的时候,可正确同时调用子类和祖先类的析构函数。
知识点:
派生类构造函数和析构函数的执行顺序
当创建对象时,编译系统会自动调用构造函数。当撤销对象时,编译系统会自动调用析构函数。
当创建派生类的对象时,首先执行基类的构造函数,然后执行派生类的构造函数。当撤销对象时,则先执行派生类的析构函数,然后再执行基类的析构函数。
class A { public: A() { printf("A "); } ~A() { printf(" ~A "); } // 增加了虚拟关键字 }; class B : public A { public: B() { printf("B "); } ~B() { printf("~B "); } }; int main(int argc, char* argv[]) { A* a = new B; delete a; return 0; } 执行结果: A B ~A
结论:析构函数本应该设置成虚函数。在父类指针指向子类对象做对象销毁时,由于析构函数不是虚函数,则delete时,父类指针只能调用父类自己的析构函数,这就造成了上述对象部分销毁的错误状况。
《Effective C++》条款 07 p44:
- 带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数
- Classes的设计目的如果不是作为base classes 使用,或不是为了具备多态性,就不该声明virtual析构函数。
父类指针指向子类对象,而子类对象却经由父类指针被删除,当父类有个non-virtual析构函数是,就会引起灾难。
C++明确指出,当子类对象经由一个父类指针被删除,而该父类带有一个non-virtual析构函数,其结果未定义--实际执行时通常发生的是对象的derived成分没有被销毁。
子类的析构函数也未能被执行。然而其base class成分通常会被销毁,于是造成了诡异的“局部销毁”对象。这可能造成资源泄露,败坏数据结构等。
二、子类指针指向父类对象
简单来说,C++的多态就是靠父类指针指向子类对象+虚函数来实现的。
父类指针指向子类对象,可以调用子类从父类继承来的那一部分,但如果父类中声明了virtual,则可以调用子类中的方法,这样就实现了多态。
而子类指针指向父类对象,可能会调用到父类中没用的方法,因此这是不对的。
例如:
class a { public: int aa }; class b:public a { public: int bb;从内存的来Class a:
---------|
|占一个int数据大小--|
|----(aa数据)------ |
|---------
Class b:
---------|---------
|占一个int数据大小--|占一个Int数据大小--|
|从a中继承而来------|---(bb数据----------|
|------------------
当定义一个基类类型的指针时
a *p;这时,这个指针指向的是a类型的数据
当p指针指向派生类的时候,因为p是a类型的指针,所以*p只解释为a类型数据的长度,即
————————-|---------
|占一个int数据大小--|占一个Int数据大小--|
|从a中继承而来------|-----(bb数据)-------|
|------------------
|------------|------------|
|-p只指向这个区域_--|
因此,当基类的指针(P)指向派生类的时候,只能操作派生类中从基类中继承过来的数据。
指向派生类的指针,因为内存空间比基类长,会导致严重了后果,所以不允许派生类的指针指向基类。而基类的指针可以指向派生类。
子类指针指向父类对象的强制转换:
b* pb = (b*)new a;
参考:
https://bbs.csdn.net/topics/390626395
https://blog.csdn.net/lijian2017/article/details/80894219
https://www.cnblogs.com/yyx1-1/p/6087440.html
https://www.cnblogs.com/rednodel/p/4122781.html