多态在C++中是一个重要的概念,通过虚函数机制实现了在程序运行时根据调用对象来判断具体调用哪一个函数。

具体来说就是:父类类别的指针(或者引用)指向其子类的实例,然后通过父类的指针(或者引用)调用实际子类的成员函数。在每个包含有虚函数的类的对象的最前面(是指这个对象对象内存布局的最前面)都有一个称之为虚函数指针(vptr)的东西指向虚函数表(vtbl),这个虚函数表(这里仅讨论最简单的单一继承的情况,若果是多重继承,可能存在多个虚函数表)里面存放了这个类里面所有虚函数的指针,当我们要调用里面的函数时通过查找这个虚函数表来找到对应的虚函数,这就是虚函数的实现原理。注意一点,如果基类已经插入了vptr, 则派生类将继承和重用该vptr。vptr(一般在对象内存模型的顶部)必须随着对象类型的变化而不断地改变它的指向,以保证其值和当前对象的实际类型是一致的。

  以上这些概念都是C++程序员很熟悉的,下面通过一些具体的例子来强化一下对这些概念的理解。

1.

#include<iostream>
using namespace std; class IRectangle
{
public:
virtual ~IRectangle() {}
virtual void Draw() = ;
}; class Rectangle: public IRectangle
{
public:
virtual ~Rectangle() {}
virtual void Draw(int scale)
{
cout << "Rectangle::Draw(int)" << endl;
}
virtual void Draw()
{
cout << "Rectangle::Draw()" << endl;
}
}; int main(void)
{
IRectangle *pI = new Rectangle;
pI->Draw();
pI->Draw();
delete pI;
return ;
}

  该段代码编译失败:

C:\Users\zhuyp\Desktop>g++ -Wall test.cpp -o test -g

test.cpp: In function 'int main()':
test.cpp:29:17: error: no matching function for call to 'IRectangle::Draw(int)'
pI->Draw(200);
^
test.cpp:29:17: note: candidate is:
test.cpp:8:18: note: virtual void IRectangle::Draw()
virtual void Draw() = 0;
^
test.cpp:8:18: note: candidate expects 0 arguments, 1 provided

C:\Users\zhuyp\Desktop>

  以上信息表明,在父类IRectangle中并没有Draw(int)这个函数。确实,在父类IRectangle中没有这样签名的函数,但是不是多态吗,new 的不是子类Rectangle吗?我们注意到指针 pI 虽然指向子类,但是本身确是父类 IRectangle 类型,因此在执行 pI->draw(200)的时候查找父类vtable,父类的vtable 中没有Draw(int)类型的函数,因此编译错误。

  如果将 pI->draw(200) 这一句修改,将pI进行一个down cast 则编译正常,dynamic_cast<Rectangle *>(pI)->draw(200); 此时调用的是子类的指针,查找的是子类的vtable,该vtable中有签名为 draw(int) 的函数,因此不会有问题。

2.

#include <iostream>
using namespace std; class Base
{
public:
~Base()
{
cout << "~Base()" << endl;
}
void fun()
{
cout << "Base::fun()" << endl;
}
}; class Derived : public Base
{
public:
~Derived()
{
cout << "~Derived()" << endl;
}
virtual void fun()
{
cout << "Derived::fun()" << endl;
}
}; int main()
{
Derived *dp = new Derived;
Base *p = dp;
p->fun();
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
cout << (void *)dp << endl;
cout << (void *)p << endl;
delete p;
p = NULL; return ;
}

  编译并运行程序:

C:\Users\zhuyp\Desktop>test.exe
Base::fun()
1
8
0x3856a0
0x3856a0
~Base()

  编译器使用的是gcc4.8.1 可以看出 p 和 pb 的值是相同的,因此可以得出结论,现代C++编译器已经没有为了性能的问题将vptr指针放在类内存模型的最前面了。

3.

#include<iostream>
using namespace std; class B
{
int b;
public:
virtual ~B()
{
cout << "B::~B()" << endl;
}
}; class D: public B
{
int i;
int j;
public:
virtual ~D()
{
cout << "D::~D()" << endl;
}
}; int main(void)
{
cout << "sizeB:" << sizeof(B) << " sizeD:" << sizeof(D) << endl;
char *ch = NULL; B *pb = new D[]; cout<<"size *pb "<<sizeof(pb)<<"\tend"<<endl; delete [] pb; return ;
}

  程序运行出错,在输出 pb 的大小之后。可见是在delete [] pb 的时候出了问题。

  我们知道释放申请的数组空间的时候需要使用 delete [] ,那 delete 怎么知道要释放多大的内存呢?delete[]  的实现包含指针的算术运算,并且需要依次调用每个指针指向的元素的析构函数,然后释放整个数组元素的内存。

  由于C++中多态的存在,父类指针可能指向的是子类的内存空间。由于上面的例子中delete [] 释放的是多态数组的空间,delete[] 计算空间按照 B 类的大小来计算,每次偏移调用析构函数是按照B类来进行的,而该数组实际上存放的是D类的指针释放的大小不对(由于 sizeof(B) != sizeof(D) ,),因此会崩溃。

C:\Users\zhuyp\Desktop>test.exe
sizeB:16 sizeD:24
size *pb 8 end

注意:本代码在64bit环境中执行的,因此 *pb 是 8.

最新文章

  1. json格式的日期格式化
  2. 服务器设置SSH 长连接
  3. Solr入门之(4)配置文件solr.xml
  4. [转]浅谈https\ssl\数字证书
  5. jquery.tmpl 用法(附上详细案例)
  6. andirod
  7. Catalyst揭秘 Day3 sqlParser解析
  8. backbone csdn
  9. 转:CSS布局奇淫技巧之-宽度自适应
  10. SharePoint 2013 字段属性之JSLink 转载来源(http://www.cnblogs.com/jianyus/p/3544482.html)
  11. 【我与一道水题的抗争之路】 哈理工2323 Emirp(反素数)
  12. console报错类型
  13. dotnetcore Http服务器研究(一)
  14. 跟技术胖学vue+koa
  15. 【ES6】let 命令
  16. XHTML 簡介
  17. css细节:尖角处理
  18. Android WiFi 扫描流程分析(wpa_supplicant)
  19. javascript札记
  20. 【Html基础】之&lt;h1&gt;~&lt;h6&gt; &lt;p&gt; &lt;br&gt; &lt;hr&gt;

热门文章

  1. (转)投影矩阵的推导(Deriving Projection Matrices)
  2. quartzScheduler_Worker-1] but has failed to stop it. This is very likely to create a memory leak解决
  3. Hadoop map和reduce数量估算
  4. Determining if a point lies on the interior of a polygon
  5. 直播技术资源站 http://lib.csdn.net/base/liveplay/structure
  6. CROC 2016 - Final Round [Private, For Onsite Finalists Only] C. Binary Table FWT
  7. Android开发:第五日番外——过时的函数和被横杠的函数
  8. Android开发:第四日番外——Assets文件夹和RAW文件夹区别
  9. 【转】重新封装FetchUrl函数一枚,支持COOKIES,喜欢领走~!
  10. Asp.net中的ajax回调模式(ICallbackEventHandler)