如何访问成员?

前面介绍了C++对象模型,下面介绍C++对象模型的对访问成员的影响。其实清楚了C++对象模型,就清楚了成员访问机制。下面分别针对数据成员和函数成员是如何访问到的,给出一个大致介绍。

对象大小问题

其中:3个类中的函数都是虚函数

l  Derived继承Base

l  Derived_Virtual虚继承Base

void test_size()
{
Base b;
Derived d;
Derived_Virtual dv;
cout << "sizeof(b):\t" << sizeof(b) << endl;
cout << "sizeof(d):\t" << sizeof(d) << endl;
cout << "sizeof(dv):\t" << sizeof(dv) << endl;
}

  输出如下:

因为Base中包含虚函数表指针,所有size为4;Derived继承Base,只是扩充基类的虚函数表,不会新增虚函数表指针,所以size也是4;Derived_Virtual虚继承Base,根据前面的模型知道,派生类有自己的虚函数表及指针,并且有分隔符(0x00000000),然后才是虚基类的虚函数表等信息,故大小为4+4+4=12。

#pragma once
class Empty
{
public:
Empty(void);
~Empty(void);
};

Empty p,sizeof(p)的大小是多少?事实上并不是空的,它有一个隐晦的1byte,那是被编译器安插进去的一个char。这将使得这个class的两个对象得以在内中有独一无二的地址

数据成员如何访问(直接取址)

跟实际对象模型相关联,根据对象起始地址+偏移量取得。

静态绑定与动态绑定

程序调用函数时,将使用那个可执行代码块呢?编译器负责回答这个问题。将源代码中的函数调用解析为执行特定的函数代码块被称为函数名绑定(binding,又称联编)。C语言中,这非常简单,因为每个函数名都对应一个不同的额函数。在C++中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而编译器可以再编译过程中完成这种绑定,这称为静态绑定(static binding),又称为早期绑定(early binding

然而虚函数是这项工作变得更加困难。使用哪一个函数不是能在编译阶段时确定的,因为编译器不知道用户将选择哪种类型所以,编译器必须能够在程序运行时选择正确的虚函数的代码,这被称为动态绑定(dynamic binding),又称为晚期绑定(late binding

使用虚函数是有代价的,在内存和执行速度方面是有一定成本的,包括:

l  每个对象都将增大,增大量为存储虚函数表指针的大小;

l  对于每个类,编译器都创建一个虚函数地址表;

l  对于每个函数调用,都需要执行一项额外的操作,即到虚函数表中查找地址。

虽然非虚函数比虚函数效率稍高,单不具备动态联编能力。

函数成员如何访问(间接取址)

跟实际对象模型相关联,普通函数(nonstatic、static)根据编译、链接的结果直接获取函数地址;如果是虚函数根据对象模型,取出对于虚函数地址,然后在虚函数表中查找函数地址。

多态如何实现?


多态的实现

多态(Polymorphisn)在C++中是通过虚函数实现的。通过前面的模型【参见“有重写的单继承”】知道,如果类中有虚函数,编译器就会自动生成一个虚函数表,对象中包含一个指向虚函数表的指针。能够实现多态的关键在于虚函数是允许被派生类重写的,在虚函数表中,派生类函数对覆盖(override)基类函数。除此之外,还必须通过指针或引用调用方法才行,将派生类对象赋给基类对象。

上面2个类,基类Base、派生类Derived中都包含下面2个方法:

void print() const;

virtual void print_virtual() const;

这个2个方法的区别就在于一个是普通成员函数,一个是虚函数。编写测试代码如下:

void test_polmorphisn()
{
Base b;
Derived d; b = d;
b.print();
b.print_virtual(); Base *p;
p = &d;
p->print();
p->print_virtual();
}

  

根据模型推测只有p->print_virtual()才实现了动态,其他3调用都是调用基类的方法。原因如下:

l  b.print();b.print_virtual();不能实现多态是因为通过基类对象调用,而非指针或引用所以不能实现多态。

l  p->print();不能实现多态是因为,print函数没有声明为虚函数(virtual,派生类中也定义了print函数只是隐藏了基类的print函数。

为什么析构函数设为虚函数是必要的

析构函数应当都是虚函数,除非明确该类不做基类(不被其他类继承)。基类的析构函数声明为虚函数,这样做是为了确保释放派生对象时,按照正确的顺序调用析构函数。

从前面介绍的C++对象模型可以知道,如果析构函数不定义为虚函数,那么派生类就不会重写基类的析构函数,在有多态行为的时候,派生类的析构函数不会被调用到(有内存泄漏的风险!)。

例如,通过new一个派生类对象,赋给基类指针,然后delete基类指针。

void test_vitual_destructor()
{
Base *p = new Derived();
delete p;
}

  如果基类的析构函数不是虚函数:

注意,缺少了派生类的析构函数调用。把析构函数声明为虚函数,调用就正常了:

C++对象模型6--对象模型对数据访问的影响的更多相关文章

  1. C++对象模型--C++对象模型

    何为C++对象模型? 部分: 1       语言中直接支持面向对象程序设计的部分 2       对于各种支持的底层实现机制 语言中直接支持面向对象程序设计的部分,如构造函数.析构函数.虚函数.继承 ...

  2. SharePoint服务器端对象模型 之 对象模型概述(Part 1)

    在一个传统的ASP.NET开发过程中,我们往往会把开发分为界面展现层.逻辑业务层和数据访问层这三个层面.作为一个应用开发平台,SharePoint是微软在直观的开发能力和自由的扩展能力之间,取到的一个 ...

  3. SharePoint服务器端对象模型 之 对象模型概述(Part 2)

    (三)Url 作为一个B/S体系,在SharePoint的属性.方法参数和返回值中,大量的涉及到了Url,总的来说,涉及到的Url可以分为如下四类: 绝对路径:完整的Url,包含了协议头(http或h ...

  4. 对数据访问层的重构(及重构中Perl的应用)

    以前上学的时候,听到“一个学生在毕业后刚刚开始编程的头几年中,写出的代码多半是垃圾”这样的说法,均不屑一顾.现在工作一年多了,越发感觉自己代码中疏漏处甚多,故近来常做亡羊补牢的重构之举.拿自己4个月前 ...

  5. C++ 系列:C++ 对象模型

    1      何为C++对象模型 C++对象模型可以概括为以下2部分: 1.语言中直接支持面向对象程序设计的部分: 2.对于各种支持的底层实现机制 语言中直接支持面向对象程序设计的部分,如构造函数.析 ...

  6. C++对象模型详解

    原文链接:吴秦大神的C++对象模型. 何为C++对象模型? C++对象模型可以概括为以下2部分: 1.语言中直接支持面向对象程序设计的部分: 2.对于各种支持的底层实现机制. 语言中直接支持面向对象程 ...

  7. C++对象模型(虽然在GCC下很大的不同,但是先收藏)

    C++对象模型 何为C++对象模型? 部分: 1.        语言中直接支持面向对象程序设计的部分 2.        对于各种支持的底层实现机制 语言中直接支持面向对象程序设计的部分,如构造函数 ...

  8. c++学习书籍推荐《深度探索C++对象模型》下载

    百度云及其他网盘下载地址:点我 百度云及其他网盘下载地址:点我 编辑推荐 如果你是一位C++程序员,渴望对于底层知识获得一个完整的了解,那么这本<深度探索C++对象模型>正适合你 作者简介 ...

  9. PowerDesigner(八)-面向对象模型(用例图,序列图,类图,生成Java源代码及Java源代码生成类图)(转)

    面向对象模型 面向对象模型是利用UML(统一建模语言)的图形来描述系统结构的模型,它从不同角度实现系统的工作状态.这些图形有助于用户,管理人员,系统分析人员,开发人员,测试人员和其他人员之间进行信息交 ...

随机推荐

  1. Exec sql/c

    Exec sql/c 利用高级语言的过程性结构来弥补SQL语言实现复杂应用方面的不足. 嵌入SQL的高级语言称为主语言或宿主语言. 在混合编程中,SQL语句负责操作数据库,高级语言语句负责控制程序流程 ...

  2. [LeetCode][Python]Largest Number

    # -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com'https://oj.leetcode.com/problems/largest ...

  3. [LeetCode][Python]Tow Sum

    # -*- coding: utf8 -*-'''https://oj.leetcode.com/problems/two-sum/ Given an array of integers, find ...

  4. centos6.5 openvpn安装配置

    http://m.jb51.net/?host=www.jb51.net&src=http%3A%2F%2Fwww.jb51.net%2Fsoftjc%2F150885.html

  5. 特卖电商俏物悄语或面临破产 ihush域名夭折?:域名新闻:域名门户:eName.CN

    特卖电商俏物悄语或面临破产 ihush域名夭折?:域名新闻:域名门户:eName.CN 特卖电商俏物悄语或面临破产 ihush域名夭折?

  6. 请问下mtk双卡手机怎样发短信是怎样选择sim卡来发(双卡都可用的情况下)?

    如题,我如今可以获取双卡状态,当仅仅有单一卡的时候可以指定sim卡进行发短信,可是双卡都可用的情况下,程序就默认使用卡1发短信了.即使指定了sim卡编号.

  7. Linux下安装Oracle的过程和涉及的知识点-系列4

    10.使用rpm安装包 假设本地有现成的相关包,能够直接使用rpm安装.rpm rpm包名,但有时会出现它须要其他包的支持,这时若须要忽略此提示.强行安装,运行rpm -i --force --nod ...

  8. C++编程技术之 异常处理(上)

    增强错误恢复能力是提高代码健壮性的最有力途径之一 之所以平时编写代码的时候不愿意去写错误处理,主要是由于这项工作及其无聊并可能导致代码膨胀,导致的结果就是本来就比较复杂的程序变得更加复杂.当然了,前面 ...

  9. 自学JavaScript的几个例子

    学习了广泛使用的浏览器脚本JavaScript和HTML的DOM模型(也是用JavaScript实现),下面给出两个自己学习时的例子,具体JavaScript语法请参考W3C相关网页(http://w ...

  10. 试用cmd markdown

    欢迎使用 Cmd Markdown 编辑阅读器 我们理解您需要更便捷更高效的工具记录思想,整理笔记.知识,并将其中承载的价值传播给他人,Cmd Markdown 是我们给出的答案 -- 我们为记录思想 ...