C++对象模型——Data Member的存取(第三章)

3.3 Data Member的存取

    已知下面这段代码:
Point3d origin;
origin.x = 0.0;
  x的存取成本是什么?
    答案视x和Point3d如何声明而定,x可能是个 static member,也可能是个nonstatic member.Point3d可能是个独立(非派生)的 class,也可能从另一个单一的base class 派生而来;虽然可能性,但它甚至可能是从多重继承或虚拟继承而来.下面数节将依次检验每一种可能性.
    先看这样一个问题,如果有两个定义,origin和pt:
Point3d origin, *pt = &origin;
    用它们来存取data members,像这样:
origin.x = 0.0;
pt->x = 0.0;
    通过origin存取和通过pt存取,有很大差别吗?

Static Data Member

    static Data member被编译器提取于 class 之外,并被视为一个global变量(但只在 class 生命范围内可见). 每一个member的存取权限(private,public,protected)以及与 class 的关联,并不会导致任何空间上或执行时间上的额外负担--不论是在个别的 class objects或者是在 static data member本身.
    每一个 static data member只有一个实体,存放在程序的data segment中,每次程序参阅 static member,就会被内部转化为对该唯一的 extern 实体的直接参考操作,例如:
// origin.chunkSize = 250;
Point3d::chunkSize = 250;
// pt->chunkSize = 250;
Point3d::chunkSize = 250;
    从指令执行的观点来看,这是C++语言中"通过一个指针和通过一个对象来存取member,结论完全相同"的唯一一种情况.这是因为"经由member selection operators(.运算符)对一个 static data member进行存取操作"只是语法上的一种便宜行事而已.member其实并不在 class object中,因此存取 static members并不需要通过 class object.
    但如果chunkSize是一个复杂继承关系中继承而来的member呢?或许它是一个"virtual base class的virtual base class"的member,那也无关紧要,程序中对于 static members还是只有唯一一个实体,而其存取路径还是那么直接.
    如果 static data member的存取是经由函数调用而被存取呢?例如:
foobar().chunkSize = 250;
    调用foobar()会发生什么事情?因为ARM并未指定foobar()是否必须被求值,因此没有人知道会发生什么事情.下面是一种可能的转化:
(void) foobar();
Point3d.chunkSize = 250;
    若取一个 static data member的地址,会得到一个指向其数据类型的指针,而不是一个指向其 class member的指针, 因为 static member并不内含在一个 class object中,例如:
&Point3d::chunkSize;
    会获得类型如下的内存地址:
const int *
    如果有两个classes,每一个都声明了一个 static member freeList,那么当它们都被放在程序的data segment时,就会导致名称冲突.编译器的解决办法是暗中对每一个 static data member编码,以获得一个独一无二的程序识别代码.

Nonstatic Data Members

    Nonstatic data members直接存放在每一个 class object中,除非经由明确的(explicit)或隐式的(implicit)class object,没有办法直接存取它们. 只要程序员在一个member function中直接处理一个nonstatic data member,所谓"implicit class object"就会发生,例如:
Point3d Point3d::translate(const Point3d &pt) {
x += pt.x;
y += pt.y;
z += pt.z;
}
    表面上所看到的对于x,y,z的直接存取,事实上是经由一个"implicit class object"(由 this 指针表达)完成.事实上这个函数的参数是:
// member function的内部转化
Point3d Point3d::translate(Point3d *this, const Point3d &pt) {
this->x += pt.x;
this->y += pt.y;
this->z += pt.z;
}
    欲对一个nonstatic data member进行存取操作,编译器需要把 class object的起始地址加上data member的偏移量(offset).例如:
origin._y = 0;
    那么地址 &origin._y 将等于:
&origin + (&Point3d::_y - 1);
    注意其中的-1操作,指向data member的指针,其offset值总是被加上1,这样可以使编译系统区分出"一个指向data member的指针,用以指出class的第一个member"和"一个指向data member的指针,没有之处任何member"两种情况.
    每一个nonstatic data member的偏移量(offset)在编译时期即可获知,甚至如果member属于一个base class subobject(派生自单一或多重继承串链)也是一样. 因此,存取一个nonstatic data member,其效率和存取一个C struct member或一个nonderived class 的member是一样的.
    现在看看虚拟继承.虚拟继承将为"经由base class subobject存取class members"导入一层新的间接性.例如:
Point3d *pt3d;
pt3d->_x = 0.0;
    其执行效率在_x是一个 struct member,一个 class member,单一继承,多重继承的情况下都完全相同.但如果_x是一个 virtual base class 的member,存取速度会比较慢一点.以两种方法存取x坐标,像这样:
origin.x = 0.0;
pt->x = 0.0;
    "从origin存取"和"从pt存取"有什么重大的差异?答案是 "当Point3d是一个derived class,而在其继承结构中有一个virtual base class,并且被存取的member是一个从该virtual base class继承而来的member时,就会有重大的差异".这时 不能够说pt 必然指向哪一种 class type,所以这个存取操作必须延迟至执行期经由一个额外的间接导引,才能够解决.但如果使用origin,就不会有这些问题,其类型无疑是Point3d class,而即使它继承自 virtual base class, member的offset位置也在编译期间就固定了.一个积极进取的编译器甚至可以静态地经由origin就解决掉对x的存取.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值