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"两种情况.
版权声明:本文为博主原创文章,未经博主允许不得转载。