多态在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. python ide: pycharm

    1, 设置python路径 2,运行py文件 https://www.jetbrains.com/help/pycharm/2016.1/creating-and-running-your-first ...

  2. j$(function() j$(document).ready 区别

    $j(document).ready(function(){}); // 或者  $j(function(){}); 第一个是直接使用Jquery调用function,第二个是在文档加载完毕后才去调用 ...

  3. Java多线程(3) Volatile的实现原理

    Volatile变量 在程序设计中,尤其是在C语言.C++.C#和Java语言中,使用volatile关键字声明的变量或对象通常拥有和优化和(或)多线程相关的特殊属性.通常,volatile关键字用来 ...

  4. Java 7 jstat – JVM Statistics Monitoring Tool【翻译】

    原文地址:Java 7 jstat 本文内容 语法 参数 描述 虚拟机标识符 选项 一般选项 输出选项 示例 先发出来,然后慢慢翻译~ 语法 jstat [ generalOption | outpu ...

  5. Swift中对C语言接口缓存的使用以及数组、字符串转为指针类型的方法

    由于Swift编程语言属于上层编程语言,而Swift中由于为了低层的高性能计算接口,所以往往需要C语言中的指针类型,由此,在Swift编程语言刚诞生的时候就有了UnsafePointer与Unsafe ...

  6. 笔记 - 本地拦截genymotion或者Android模拟器的网络请求

    我们在主机上面运行了Burp或者fiddler,那么代理已经监听在本机的8080端口了. 那么我们需要在模拟器中进行如下设置: 1.在设置中,长按当前连接的wifi网络,弹出如下: 2. 点击修改网络 ...

  7. CGI、FastCGI 知识总结

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  8. iOS-Xcode上传后iTunes Connect构建版本不显示

    在升级到Xcode8版本以后大多数人会碰到这个问题-开开心心开发好一款app以后上传到iTunes后台提交审核,然而iTunes Connect后台活动栏里没有出现我们上传的app,确切的说是显示一会 ...

  9. [原]OpenGL基础教程(四)VBO+纹理绘制四边形

    工程下载地址:http://pan.baidu.com/s/1ntr7NHv 提取码:yf1h 一.本文牵扯知识点梳理: (1)VBO (2)纹理 (3)libpng(加载png) (4)shader ...

  10. centos 7.0 下触发器乱码的解决

    [root@iZ62jhlmmZ ~]# uname -a Linux iZ62jhlmsymZ 3.10.0-123.13.2.el7.x86_64 #1 SMP Thu Dec 18 14:09: ...