先看代码:
1 #include2 using namespace std; 3 4 class Parent { 5 public: 6 Parent() :a(100), b(200), c(300) 7 { 8 9 p = new char[10]; 10 //strcpy(p, "abc"); 11 cout << "parent 无参构造。。。\n"; 12 } 13 Parent(int test) :a(1000), b(2000), c(3000) 14 { 15 p = new char[10]; 16 //strcpy(p, "abc"); 17 cout << "parent 有参构造。。。\n"; 18 } 19 ~Parent() 20 { 21 delete[] p; 22 cout << "Parent 析构。。。\n"; 23 } 24 int a; 25 int b; 26 int c; 27 char *p; 28 void p_print() 29 { 30 cout << "a b c is" << a << " " << b << " " << c << endl; 31 } 32 33 }; 34 class Child1 : public Parent 35 { 36 public: 37 Child1() :Parent(1),a(10), b(0), c(0) 38 { 39 p = new char[10]; 40 // strcpy(p, "abc"); 41 cout << "child1 构造\n"; 42 } 43 ~Child1() 44 { 45 delete[] p; 46 cout << "child1 析构,,,\n"; 47 } 48 void c1_print() 49 { 50 cout << "a b c is" << a << " " << b << " " << c << endl; 51 } 52 53 int a; 54 int b; 55 int c; 56 char *p; 57 }; 58 class Child2 : public Child1 59 { 60 public: 61 Child2() :Child1(), b(2), c(3) 62 { 63 p = new char[10]; 64 //strcpy(p, "abc"); 65 cout << "child2 构造\n"; 66 } 67 ~Child2() 68 { 69 delete[] p; 70 cout << "child2 析构,,,\n"; 71 } 72 void c2_print() 73 { 74 cout << "a b c is" << Parent::a << " " << b << " " << c << endl; 75 } 76 //int a; 77 int b; 78 int c; 79 char *p; 80 }; 81 /* 82 class Child3 : public Child1, public Child2 83 { 84 public: 85 Child3() : Child1(), Child2(), b(20), c(30) { cout << "child 构造\n"; } 86 ~Child3() 87 { 88 cout << "child 析构,,,\n"; 89 } 90 void c3_print() 91 { 92 cout << "a b c is" << a << " " << b << " " << c << endl; 93 } 94 //int a; 95 int b; 96 int c; 97 }; 98 */ 99 void play()100 {101 Child2* c2 = new Child2;102 delete c2;103 }104 int main()105 {106 //Child2* c2 = new Child2;107 play();108 return 0;109 }
这样是没问题的(c++编译器会以构造相反的顺序执行析构函数),但是,在很多时候,我们不能在调用函数末尾就delete掉这个内存,还需要和后续交互。比如作为函数参数,为了实现多态,我们函数参数是父类的指针,所以更常见和一般的设计思维是更改paly和main函数如下:
1 void play(Parent* p) 2 { 3 delete p; 4 } 5 int main() 6 { 7 Child2* c2 = new Child2; 8 play(c2); 9 return 0;10 }
运行结果显示内存泄漏了,只析构了父类;所以我们有那么一种需求,要想和多态的效果一样,传什么指针去,自动析构应该析构的东西,更改代码,实现虚析构函数。
只用更改基类的析构函数,加上virtual关键字:
1 virtual ~Parent()2 {3 delete[] p;4 cout << "Parent 析构。。。\n";5 }
这样就没有内存泄漏了。
知识简要:
如果基类的析构函数不是虚函数,则delete一个指向派生类对象的基类指针将产生未定义的行为。之前我们介绍过一个准则,如果一个类需要析构函数,那么它同样需要拷贝和赋值操作。基类的析构函数并不遵循上述准则,它是一个重要的例外。