本帖最后由 倾心莫若初见 于 2016-10-18 17:00 编辑
我们先来看一个代码,这是在继承与虚函数学生过程中发生的一个错误,涉及到了C++的对象内存的知识,因为这方面知识比较复杂,这里不做过多的介绍,只简单分析一下出错原因。 class Base { public: void fun() { Cout << “Base::fun()” << endl; } ~Base() //虚析构函数 { cout << ”Base::~Base” << endl; } }; class Child:publicBase //继承Base类 { Public: virtual void fun() //虚函数 { Cout << “Child:fun()” <<endl; } }; intmain() { Base *p = new Child; //新建一个子类的对象,赋值给一个父类指针 p->fun(); delete p; //通过父类指针释放内存 return 0; } VS运行程序时,发生如下错误: 通过提示发现,VS提示应该是内存方面的错误。而且对于上面的代码来说,父类中的fun函数不是虚函数,而子类中的fun函数是虚函数,所以p->fun也是会调用父类的fun函数。 那么为什么会出现内存释放的错误呢?重新写一个main函数如下: intmain() { Child *c = new Child; //在堆上创建一个子类对象 Base *p = c; //将子类对象指针赋值给一个父类对象指针 cout << c << " "<< p << endl; //打印信息 p->fun(); //通过父类指针来调用fun函数 delete p; //通过父类指针释放内存 return 0; } 再次运行以上代码,结果如下: 我们从打印的信息可以看到,Child对象指针值为0x01393FD0,Base对象指针为0x1393FD4,通过打印信息发现两个值并不一样,这样释放内存时产生了错误。 原因如下: (1) 存在虚函数的类对象中会隐藏了一个虚指针,虚指针存放在对象内存的开始位置,这里子类中有一个虚指针而父类中没有; (2) 子类中有一块内存布局和父类对象的内存布局是一样的,但是这块内存肯定不是子类对象的起始位置,所以将子类对象指针赋值给一个父类对象指针时,为了操作上不产生错误,会把这块和父类内存布局相同的位置赋值给父类指针,因而发生了内存的偏移; (3) 在堆上分配的子类对象内存,如上面代码起始地址是0x01393FD0,这里是通过delete父类指针来释放内存,而父类指针的值为0x1393FD4,这样释放内存中检测不是正确的起始位置而发生了错误。 解决方法: 要解决以上问题,就要想办法让子类指针赋值给父类指针时,两个指针的值是一个样,这里我们可以在父类中设置任意一个虚函数(将父类中的fun设置为虚函数或者将父类的析构函数设置为虚函数),这样父类和子类中都有虚指针,赋值时不会发生地址的偏移。 为了防止出现以上错误,我们代码中一定要多加注意。尽量不要在子类中设置虚函数和父类中的普通函数重名,另外还有一个编码小技巧,如果一个类中有虚函数或者纯虚函数时,要将其析构函数设置为虚析构函数,以防发生内存泄漏等问题。
|