C++ 知识整理 (17) 数据语意学 Sematics of Data
对象的大小
对于A:
有一个隐含1byte, 使得该类的对象在内存分配时占用不同的内存地址(GCC为1, VS为0)
对于B,C:
语言本身的额外负担. 当有virtual base class时,派生类中有bptr, 指向virtual base class subpbject或者偏移表格
编译器对特殊情况所提供的优化处理
Alignment调整, alignment=4, 使BUS到最大吞吐效率
对于D:
两个bptr组成D
对于
class A{};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
有
sizeof(A) == 1; sizeof(B) == 4; sizeof(C) == 4; sizeof(D) == 8;
注: static member/function, non-static function不加入类对象大小.
Data Member Layout数据成员布局
non-static data member不论访问限制, 仅按照出现的顺序, 由地址从低到高排列, 不一定连续, 是由于alignment的调整.
若有v.f. [...]
C++知识整理 (16) 构造函数语意学 Sematics of Constructor
Default Constructor的构造操作
当编译器需要, 非程序需要的时候, 会合成出一个constructor, 一般情况下, 若无用户自定义的构造函数, 则会implicit声明一个trivial default constructor.
带有Default Constructor的Member Class Object
若某class没有任何constructor, 但它内含一个member object, 而后者有default constructor, 那么前者的implicit default constructor是nontrivial
在C++的各编译模块(文件)中, 为防止在各个文件合成多个default constructor, 则合成为inline(静态内联展开), 如果无法合成为inline, 则合成为explicit static.
若用户自定义了default constructor, 但没有显示初始化类所含的类对象, 则编译器在D.C.前按照member object的声明顺序安插初始化代码.
若没有自定义D.C. 则生成nontrivial constructor, 用于安插代码.
带有Default Constructor的Base Class
继承类的基类带D.C., 而继承类没有D.C., 则会合成nontrivial D.C.
合成的D.C., 将隐式调用基类的D.C., 若自定义多个constructor, 而没有D.C., 则将扩展代码, 而不会合成新的D.C.
按照派生列表的顺序构造基类.
带有Virtual Function的Class
Class声明(或继承)了一个Virtual Function.
Class有一个或多个的virtual base class, 编译期间的两个扩展操作: (1) 创建一个vtbl (2) object中创建vptr. 编译器为object的vptr设定初值, 放置适当的vtbl地址
带有一个Virtual Base Class的Class
不同编译器之间差异很大, [...]
C++知识整理 (15) C++对象布局
加上封装后的布局成本(Layout Costs for Adding Encapsulation), 封装并没有增加成本, 类对象内含数据成员, 成员函数不在类对象中, 非内联成员函数仅存在一份函数实体, 而内联函数在调用出展开. C++在布局和存取时间上的主要额外负担在于虚函数, 虚基类和多重继承.
C++对象模型(C++ Object Model)
两种数据成员(static和non_static)
三种成员函数(static, non_static, virtual)
简单对象模型(Simple Object Model)
类对象含有一组slot, slot中存储每个元素(数据和函数)的指针, 损失空间和执行期效率, 对象大小 = 指针长度 * 成员(数据+函数)个数
表格驱动对象模型(Table-driven Object Model)
对所有类的所有对象的布局保持一致, 对象含有两个指针, 分别指向Data Member Table和Member Function Table, 前者直接含有数据值, 后者含有指向函数的指针.
表格驱动对象模型
non_static data member被放在每一个object中, static data member, static function member, non_static function member都在object之外, 对于virtual function: 1. 每个class产生一堆指向VF的指针; 2. 每个object被添加一个vptr, [...]
C++知识整理 (14) 特殊工具与技术
类成员的指针.
class test
{
int i;
void print(int i) const{}
};
定义
int test::*pi = &test::i; // 相对地址指针
void (test::*pf) (int) const = &test::print;
使用
test t;
int a = t.*pi;
(t.*pf)(int());
RTTI
<typeinfo> 定义typeid(e)操作符
只有当typeid操作数是带虚函数的类类型对象时, 才返回动态类型信息, 测试指针相对于指针指向的对象, 返回静态类型.
volatile变量的值, 不由程序本身完全控制, 如系统时钟.
C++知识整理 (13) 用于大型程序的工具
异常对象通过复制被抛出的表达式的结果来创建, 该结果必须是可以复制的类型.
当抛出一个表达式的时候, 被抛出的对象的静态编译时类型(catch语句中的形参类型)将决定异常对象的类型, 同样对抛出的指向基类的指针, 解引用后事基类对象, 其他的继承对象部分被分割.
抛出异常栈展开过程中每跳出一个函数域, 就会将此函数域内局部对象析构掉, 从另外方面说明, 返回对象指针不好, 但通常不撤销内置类型的变量.
析构函数不能抛出异常, 若抛出异常则会调用terminate(), 一般terminate()会调用abort()推出, 若有为捕获的异常也会调用terminate()
catch语句的形参必须是完全类型, 仅声明不行, 形参转换的方式支持: 1. 非const到const. 2. 派生类到基类. 3. 数组/函数到指针.
异常对象本身是被抛出对象的副本, catch形参是引用的话就少复制一次
确定调用某catch后, 则跳过同级catch.
catch all语法: catch(…){}
函数声明中必须制定异常规范, 重复声明和定义要保持一致.
基类的虚函数中抛出异常, 在派生类对应的虚函数抛出的异常, 只能更严格.
class Base {virtual foo() throws excp_base};
class Derived{virtual foo() throws excp_derived};
命名空间可累计
接受类类型形参(或类类型指针及引用形参)的函数(包括operator)以及与类本身定义在同一命名空间中的函数(包括operator), 在用类类型对象(或类类型指针及引用)作为实参的时候是可见的.
多重继承中, 多个基类构造次序按照派生列表的顺序构造, 而不按照初始化顺序.
派生类的引用或指针可以转换为任一基类的引用或指针, 且转换级相同.
class B;
class D1 : virtual public B;
class D2 : virtual public B;
class DD : public [...]

