爱技术 & 爱分享
爱蛋蛋 & 爱生活

Data Member 存取

这次我们想讨论一下,Data Member 的存取开销。

如下代码:

Point3D origin;

origin.x=0.0;

x的存取开销是什么? 这个问题不能一概而论,必须视 x 和 Point3D如何定义而定。x可能是 static member 也可能是 nonstatic member。而Point3D可能是一个独立的class , 也可能是从另一个单一的 base class 派生而来,甚至也有可能是从多重继承和虚拟继承而来。

再者 以下两者有什么重大差异?

Point3D origin , *pt =&origin;
origin.x=0.0;
pt->x=0.0;

Static Data Members

我们都知道,Static Data Member 是被提出于 class 之外的,并被视为一个 global 变量(但只在 class 范围内可见)。每一个 Static Data Members 的读写权限(private,protect,public),以及与 class 的关联,并不会导致任何的额外开销(时间空间上的)。

每一个 Static Data Members 都只有一个实例,存放在 程序的 data segment 之中。每次使用该变量时,都会被转换为对唯一extern实例的直接操作。

所以如果 x 是 Static Data Members 那么无论是Point3D::x=0.0,origin.x=0.0;还是pt->x=0.0;效率都是一样的,并且不会因为 class 的缘故而带来额外开销。

什么? 你说他是多重继承或者是虚继承过来的怎么办? 没关系的,只要你敢来我都一样对待,都是转换为访问唯一实例,存取路径还是那么的直接。

再来个更狠的,如果是写成这样子呢,foo().x =2;(假设 foo() 返回Point3D类型的object)。C++ Standard 明确规定 foo()函数是必须被调用的。

那么以上的情况,其实是可能被转换成了 (void)foo(); Point3D::x=2;

而且 如果是对一个 class 的 static data member 取址的话,会的到一个指向其数据类型的指针,而不是指向其 class member 的指针,因为 static member 并不含在一个 class object 之中;

&Point3D::x;会的到类型如下的内存地址 const float*

Nonstatic Data Member

Nonstatic Data Member直接存放在每一个 class object之中。除非经由显式或隐式的class object,否则的话 无法直接操作它们。在class member function中处理 Nonstatic Data Member 被称为隐式 class object 操作。

比如:

Point3D Point3D::Translate(const Point3D &pt)
{
    x+=pt.x;
    y+=pt.y;
    z+=pt.z;
}

上面我们看到的x ,y ,z的直接操作,其实是经过 this 指针隐式完成的。 所以事实上这个函数是这样子的:

Point3D Point3D::Translate(Point3D *const 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.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 或者 一个 nondrived class 的 member是一样的。

现在我们看看虚拟继承的情况。 虚拟继承将由”经由 base class subobject 操作 class member”,所以有一层的新的间接性:

Point3D *pt;
pt->x=0.0;

上述代码在,x是一个 struct member 和一个class member、单一继承、多重继承的情况下是完全相同的。 但,如果x 是一个 virtual base class 的member,操作时会有额外的开销的,这是因为虚继承下的间接性导致的。

那么我们再来看一下origin.x=0.0;pt->x=0.0;的差异吧。

以上两者的差异只有当 Point3D是一个 drived class 而且其继承结构中有一个 virtual base class ,并且操作的 member 是一个从 virtual base class 继承而来的member时才会体现。

因为此时我们不能明确的指出,pt到底是指向哪一种 class type,所以在编译时期我们也不知道这个member的真正offset位置,所以这个操作必须推迟到执行期,然后经由一个额外的间接引导才能完成。

但是对于 origin来说,就不会有这个问题,其类型已经被指定为 Point3D class,而即使它继承自 virtual base class, member的 offset 的位置在编译时期就固定了。所以如果更为聪明的编译器,甚至可以静态的由origin 就解决掉对 x 的操作。

赞(1) 传送门
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。编程日志 » Data Member 存取
分享到: 更多 (0)

游戏 && 后端

传送门传送门