多态在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.

C++ 多态的实现原理与内存模型的更多相关文章

  1. Java内存模型深度解读

    Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的.Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型. 如果你想设计表现良好的并发 ...

  2. Java内存模型---并发编程网 - ifeve.com

    Java内存模型 转自:http://ifeve.com/java-memory-model-6/ 原文地址  作者:Jakob Jenkov 译者:张坤 Java内存模型规范了Java虚拟机与计算机 ...

  3. JVM内存模型及内存分配过程

    一.JVM内存模型 JVM主要管理两种类型内存:堆(Heap)和非堆(Permanent区域). 1.Heap是运行时数据区域,所有类实例和数组的内存均从此处分配.Heap区分两大块,一块是 Youn ...

  4. Java并发编程(四)-- Java内存模型

    Java内存模型 前面讲到了Java线程之间的通信采用的是共享内存模型,这里提到的共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入何时对另一个线程可见.从抽象的角 ...

  5. 11、Java并发性和多线程-Java内存模型

    以下内容转自http://ifeve.com/java-memory-model-6/: Java内存模型规范了Java虚拟机与计算机内存是如何协同工作的.Java虚拟机是一个完整的计算机的一个模型, ...

  6. 【转】Java 内存模型及GC原理

    一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能 ...

  7. Java内存模型原理总结(转自51CTO)

    转载地址:http://developer.51cto.com/art/201811/587220.htm [51CTO.com原创稿件]这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关 ...

  8. Java 内存模型、GC原理及算法

    Java 内存模型.GC原理:https://blog.csdn.net/ithomer/article/details/6252552 GC算法:https://www.cnblogs.com/sm ...

  9. Java 内存模型及GC原理 (转载)

    一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能 ...

随机推荐

  1. C#Winform程序如何发布并自动升级(图解)

    C#Winform程序如何发布并自动升级(图解)     有不少朋友问到C#Winform程序怎么样配置升级,怎么样打包,怎么样发布的,在这里我解释一下打包和发布 关于打包的大家可以看我的文章C# w ...

  2. VS2010几款超赞的扩展辅助工具总结

    详情请查看http://www.aehyok.com/Blog/Detail/72.html 个人网站地址:aehyok.com QQ 技术群号:206058845,验证码为:aehyok 本文文章链 ...

  3. [GO编程]GO编程环境

    GO是一个开源项目,由Google大神发明的,他主要是用于应用程序级开放,可以编译成机器码,和C++一样不需要.NET或JAVA那样的运行框架,因此是个不错的编程语言.更何况发明者都是高手中的高手,而 ...

  4. C#中扩展StringBuilder支持链式方法

    本篇体验扩展StringBuilder使之支持链式方法. 这里有一个根据键值集合生成select元素的方法. private static string BuilderSelectBox(IDicti ...

  5. C/C++在Java项目、Android和Objective-C三大平台下实现混合编程

    Android和iOS开发都支持C++开发,可以一套代码多平台使用.同时C++难以反编译的特性也可以为Android开发带来代码的保密,另一native特性也可以提高代码的运行效率. 一.为什么使用C ...

  6. 用eclipse建立servlet工程

    1.打开eclipse,选择[文件]|[新建]|[项目] 2.选择[Tomcat  Project]后单击下一步,输入项目名[TestTomcat],选择下一步,将[Can update contex ...

  7. Oracle的悲观锁和乐观锁---摘抄

    1.无论是选择悲观锁策略,还是乐观锁策略.如果一个对象被上了锁,那么该对象都会受这个锁的控制和影响.如果这个锁是个排它锁,那么其它会话都不能修改它. 2.选择悲观锁策略,还是乐观锁策略,这主要是由应用 ...

  8. Gson 和 FastJson 性能测试

    使用版本: compile 'com.google.code.gson:gson:2.7' compile 'com.alibaba:fastjson:1.2.17' 评测样板为一个People数组, ...

  9. 关于BigDecimal的使用

    为什么使用BigDecimal 使用BigDecimal首先要注意到float,double是无法支持商业计算的.只能支持工程计算.即误差允许的计算.通常float占用4个字节,32位.double占 ...

  10. Android TabActivity中onKeyDown无效问题

    @Override     public boolean onKeyDown(int keyCode, KeyEvent event)     {                  //按下键盘上返回 ...