Data Member 的存取

考察以下代码:

Point3d origin;
origin.x = 0.0;

此例中 x 的存取成本是什么? 答案则是视 x 和 Pointd 而定(别打脸, 我知道这是废话)。 具体的呢? 因为 x 可能是个 static member, 也可能是个 nonstiatic member; Point3d 可能是个独立的 class, 也可能是另一个 单一的class 派生而来;甚至可能是从多重继承或虚拟继承而来(请不要小看其他人的代码中的可能性, 你都很有可能不知道 C++ 还能这么写, 有时在这句话之后还能加一个“我勒个去”) 而下面, 咱们就来检验这每一种可能性。

在开始之前, 先抛出一个问题, 如果我们有两个定义, origin 和 pt:

Point3d origin, *pt = &origin;
//用他们来存取 data members, 像这样:
origin.x = 0.0;
pt->x = 0.0;

通过 origin 存取和 pt 存取, 有什么重大差异呢? 让我们在最后得出答案。

Static Data Members
static data members 被编译器提出于 class 之外, 并被视为一个 global 变量(但只在 class 生命范围内可见)。 每一个 member 的存取许可(无论是 public, private 还是 protected), 以及与 class 的关联, 都不会导致时间或是空间上的额外负担(无论是个别的 class object 还是 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() 会发生什么事? 在 C++ 的标准中, 没人知道会发生什么事,因为 ARM 并未指定 Foobar() 是否必须被求值(evaluated)。 cfront 的做法就是把它丢掉(= =!) 但 C++ Standard 明确要求 Foobar() 必须被求值, 哪怕其结果毫无用处, 下面就是一种可能的转化:

//你看到的
//Foobar().chunkSize = 250;
//实际上可能的代码
//对表达式求之后, 丢弃结果
(void) Foobar();
Point3d.chunkSize = 250;

若取一个 static data member 的地址, 会得到一个指向其数据类型的指针, 而不是一个指向其 class member 的指针, 因为 static member 并不在内含在一个 class object 之中,例如:

&Pointd::chunkSize;
//会得到如下的内存地址:
const int*

那如果有两个 classes, 每一个都声明了一个 static member freeList, 那么当他们都被放在程序的 data segment 时, 就会导致名称冲突, 对此编译器的解决方案是暗中对每一个 static data member 编码(这个手法有一个很美的名称: name-mangling) 疑惑的一个独一无二的程序识别代码, 有多少编译器就有多少种 name-mangling 做法。所谓的 name-mangling 做法主要就是两点:
1. 一种算法, 推导出独一无二的名称;
2. 万一编译系统必须要和使用者交谈,那些独一无二的名称可以轻易被推导回到原来的名称。

Nonstatic Data Members
Nonstatic data members 直接存放在每一个 class object 之中。 除非经由明确的(explicit) 或暗喻的(implicit) class object, 不然没有办法存取它们。只要程序员在一个 member function 中直接处理一个 nonstatic data member, 所谓 implicit class object 就会发生, 考察以下代码:

//你看到的
Point3d
Point3d::Tranlate(const Point3d &pt)
{
    _x += pt._x;
    _y += pt._y;
    _z += pt._z;
}
//实际可能的代码
//member function 的内部转化
Point3d
Point3d::Transelate(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 的偏移量, 例如:

origin._y = 0.0;

那么地址 &origin + (&Point3d::_y - 1);
要注意的是其中的 -1 操作, 指向 data member 的指针, 其偏移量(offset) 的值总是被加上 1, 这样可以使编译系统区分出 “一个指向 data member 的指针, 用以指出 class 的第一个 member” 和 “一个指向 data member 的指针, 没有指出任何member” 两种情况。其中指向 data members 的指针将在以后的博客中探讨。
每一个 nonstatic data member 的偏移量在编译时起即可获知, 甚至如果 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 是一个 stuct member, 一个 classmember, 单一继承, 多重继承的情况下都完全相同, 但如果 ——x 是一个virtual base class 的member, 存取速度会慢一点。
回到一开始的问题, 从 origin 存取和从 pt 存取 有什么重大差异? 答案是当 Pointd 是一个derived class, 而在其继承结构中有一个 virtual base class, 且被存取的 member 是一个从该 virtual base class 继承来的 member 时, 就会产生重大差异。 因为这个时候我们无法确定 pt 到底指向哪一种 class type(这就导致我们无法知道编译期这个 member 真正的 offset 位置), 所以这个存取必须延迟到执行期, 经由一个额外的间接导引, 才能解决。 但如果用 origin 就不存在这样的问题,因为 origin 的归属毫无疑问, 而他继承自 virtual base class, member 的 offset 位置也在编译时期就固定了, 一个积极进取的编译器甚至可以静态的经由 origin 就解决掉对 _x 的存取。

时间: 2024-10-23 18:43:41

Data Member 的存取的相关文章

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 派生而来;虽然可能性,但它甚至可能是从多重继承或虚拟继承而来.下面数节将依次检验每一种可能性. 先看这样一个问题,如果有两个定义,or

Data 语意学---Data member的存取效率

<深度探索C++对象模型> 对于data member来说,有两种情况 static data member数据 每一个static data member只有一个实体,存放在程序的data segment之中,无论以何种方式,无论类的继承关系如何复杂,存取路径都是非常直接 Nonstatic data members 直接存放在一个class object之中,是属于一个对象的,是需要一个叫做偏移量的值来索引的. 尤其是虚拟继承,虚拟继承将为"经由base class subobj

继承与 Data Member(2)

加上多态的情况如果我要处理一个坐标点, 而不在意这是一个 Point2d 或 Point3d 实例, 那么就需要在继承关系中提供一个 virtual function 接口: class Point2d { public: Point2d(float x = 0.0, float y = 0.0) :_x(x), _y(y){}; //x 和 y 的存取函数与前一个博客中相同 //由于对不同维度的点, 这些函数操作固定不变, 所以不必设为 virtual virtual float Z()(fl

指向 Data Member 的指针及相关的效率影响

指向 data member 的指针是一个颇有用处的语言特性, 特别是如果你需要详细调查 class members 的底层布局的话.这个调查可以帮助你决定 vptr 是放在尾端还是起始处. 另一个用途是可以用来决定 clas 中 access sections 的次序.考察以下代码, 其中有一个 virtual function, 一个 static data member, 以及三个坐标值: class Point3d { public: virtual ~Point3d(); //...

C++对象模型——&quot;继承&quot;与Data Member(第三章) .

3.4 "继承"与Data Member 在C++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base class members的总和.至于derived class members和base class members的排列次序并未在C++ Standard中强制指定:理论上编译器可以自由安排.在大部分编译器上,base class members总是先出现,但属于 virtual base class的除外. 了解这种继

C++对象模型——Data Member的绑定(第三章)

3.1    Data Member的绑定 (The Binding of a Data Member) 考虑下面这段代码: // 某个foo.h头文件,从某处含入 extern float x; // 程序员的Point3d.h文件 class Point3d { public: Point3d(float, float, float); // 问题:被传回和被设定的x是哪一个x? float X() const { return x; } void X(float new_x) const

3.1 Data Member的绑定

文章开始提出了一段示例代码,并讨论了返回哪个x的问题.然后 a)   给出了我们普遍认为正确的回答,并肯定了这个想法: b)   提醒大家,这在以前的编译器实现中,并非正确. 在早期的实现中,首先绑定的是“全局变量x”.因为该class中的x的声明尚未可见.由此导致的就是“防御性编程风格”.总结起来,具体做法有两种. c)   将所有的data member的声明提前: d)   将内联函数的实现移至class的声明之外. 对于第一点,我们容易理解.因为变量的声明中,较晚的声明总是可以覆盖之前的

Effective C# 学习笔记(原则一:始终能的使用属性(property),而不是可直接访问的Data Member)

原则一:始终能的使用属性(property),而不是可直接访问的Data Member    Always use properties instead of accessible data members. 为什么要使用属性: 1.Net的data binding只支持Property,而不支持public data member的访问 Data binding的目的就是把一个object的Property绑定到一个用户界面的control上,web control或者windows form

Data Member 的布局

考察以下代码: class Point3d { public: //... private: float _x; static List<Point3d*> *freeList; float _y; static const int chunkSize = 250; float _z; }; 其中, nonstatic data members 在 class object 中的排列顺序和其被声明的顺序是一致的, 但是任何介入的 static data members 都不会被放进布局之中.s