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, [...]
auto_ptr_ref类深入剖析
STL之<memory>所遗留下来的问题, 即在auto_ptr的实现中, 多个构造函数和操作符的重载函数中都引入了auto_ptr_ref这个模板结构(template structure), 这里我就来探讨一下这样做的必要性和可行性.
首先回顾一下最基本的C++语言特性, 即在寻找可行函数列表时, 实参向形参做类型匹配的过程中, 存在从指针(*)到常量指针(const *)的隐式转换, 然而反向的隐式转换时不存在的, 要注意的是这的常量指针是指指针所指向的值是常量, 而指针自身并非常量(* const), 另外提一下, 指针(*)到(*const)之间的匹配属于精确匹配的范畴, 即存在他们之间的相互转换, 下面给出一组例子说明.
#include <cstdlib>
#include <iostream>
using namespace std;
void foo1(int *a) {}
void foo2(const int *a) {}
void foo3(int * const a){}
void foo4(const int * const a){}
int main(int argc, char*argv[])
{
int * a = 0;
const int * b = 0;
int * const c = 0;
const int [...]
STL之<memory>
STL设计的一个重要目的就是对C++的实用性进行增强, 而C++较之当前时髦的代码托管类的语言, 比如C#或者Java, 一个最为人所诟病的特性就是动态内存的分配策略. 这一特性, 使得程序员有足够的能力来控制内存的分配与回收, 但同时也为程序员的工作量增加了无谓的负担, 并增加了内存泄露的风险. 所以为了规避这一特性所造成的麻烦, STL设计了一个实用类auto_ptr, 其实auto_ptr这个模板类的作用就是对实际对象的指针进行了封装, 并通过C++的语言机制使得该类型的对象行为就像一个普通指针一样使用, 并且由于C++语言机制保证自定义类的对象在退出其生命周期的时候会自动调用其析构函数, 加上auto_ptr类的析构函数保证了对指针所指向对象的析构工作, 这样一来, 就从形式上免去了程序员手动回收内存的麻烦.
auto_ptr类设计了一组构造函数, 包括
explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()
auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()
auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
template<class _Other> auto_ptr(auto_ptr<_Other>& _Right) _THROW0()
我们知道具有单参数的且该参数有默认值的构造函数实际上就相当于无参数的默认构造函数, 而这样的构造函数实际上定义了参数类型到该类类型的一个转换途径, 而explicit关键字压制了这种自动转换行为, 使得程序员必须给出明显的构造句式来完成构造, 不能通过将一个普通指针赋值给auto_ptr类对象的方式来初始化.对于接下来的
auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()
auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
他们合作定义了对copy contructor的完整支持, 甚至包括右值, 而其中涉及到的技术细节不属于本文的范围, 我将在另一文中阐述.
最后的模板copy constructor
template<class _Other> auto_ptr(auto_ptr<_Other>& _Right) _THROW0()
使得auto_ptr支持不同模板类型参数的auto_ptr实例之间的相互赋值初始化, 例如auto_ptr支持auto_ptr对象初始化auto_ptr对象, 但是实际上, 这样的语句仍然会导致编译报错, 原因就是本质上, 这样的初始化过程在实现上仍然是用int*来初始化float*, 而这样的初始化是被C++所禁止的, 所以就会报错, 其实, [...]

