索引
C++【对象模型】| 【01】简单了解C++对象模型的布局 C++【对象模型】|【02】构造函数何时才会被编译器自动生成? C++【对象模型】|【03】拷贝构造是如何工作的,何时才会用到呢? C++【对象模型】 | 【04】程序在内部被编译器如何转化? C++【对象模型】 | 【05】类与类之间各种关系下对数据成员的存取、绑定、布局 C++【对象模型】| 【07】构造、析构、拷贝做了哪些事? C++【对象模型】| 【08】类在执行期会处理哪些事呢? C++【对象模型】| 【09】类模板、异常处理及执行期类型识别
临时对象
class Y {
public:
Y();
~Y();
bool operator==(const Y&) const;
};
class X{
public:
X();
~X();
operator Y() const;
X getValue();
};
void test() {
X xx;
Y yy;
if(yy == xx.getValue()) {
}
}
// yy.operator==(xx.getValue().operatorY())
===>上述过程将会产生三个临时对象
X temp1 = xx.getValue();
Y temp2 = temp1.operator Y();
int temp2 = yy.operator==temp2();
===>结合if
if(temp3)....
temp2.Y::~Y();
temp1.X::~X();
对象的构造和析构
在{}内析构一般被编译器插入在离开点(如下所示);
void test(bool flag)
{
if(flag) return -1;
Point point;
switch(int(point.x())) {
case -1:
return;
case -1:
return;
case 1:
return;
default:
return;
}
}
一般obj将会在要使用的程序附近定义出,以节省非必要的对象构造和析构(如上述代码,对象在检查flag后定义定义);
全局对象
class Matrix{
public:
Matrix();
~Matrix();
};
Matrix identity;
int main() {
Matrix m1 = identity;
return 0;
}
C++中所有的全局对象都被放置在data segment;如果没有显示指定,则被初始化为0(编译期),但其构造会在程序启动才会调用;
munch方法
早期cfront提供一个可移植(UNIX)但成本较高的静态初始化方法(munch);
它是如何工作的呢?
- 为每一个需要静态初始化的文件产生一个_sti()函数,含有构造或inline expansions;
__sti()[__sti__matrix_c__identity()] { identity.Matrux::Matrix(); }
- 与之对应的产生一个__std()函数,内含析构函数;
- 提供runtime library `munch`函数:一个_main()【作为main的第一个指令】调用__sti(),一个_exit()调用__std();
【有了上述方法,那么该如何收集各个对象的sti和std函数呢】
- 使用mn命令,该命令将用于可执行文件,将对象的符号表格项目导到munch中;
- 此时munch会搜索以_sti或__std开头的名称将该函数名称添加到__sti或__std函数的跳离表格中;
- 在将这个表格写到一个小的程序文本文件;
- 在将编译重新激活,将该表格的文件加以编译,整个可执行文件被重新链接;
System V1.0提供一个快速变种办法
该方法假设可执行文件是System V COFF格式,当检验该文件并找出有__link nodes并内含一个指针,指向__sti和__std函数
文件,将它串链在一起;在将链表的根源设为一个全局性的__head obj;其内含另一种不同的_main()和_exit()将以_head为起
始的链表走访一遍;
扩充链接器和目的文件格式,以求直接支持静态初始化和内存释放操作
如System V的DLF被扩充以增加.init和.fini两个section,这两个section内含对象所需要的信息,分别对于静态初始化和释放操作;
cfront2.0后支持nonclass object的静态初始化
而支持`nonclass object`的静态初始化,在某种程度上,是支持virtual base classes的一个副产品;
静态初始化的对象,有一些缺点
- 如果支持exception handing,则此类obj不能置于try中,可能无法实现静态调用构造,因为theow会触发终止函数;
- 为了控制需要跨越模块做静态初始化的对象的相依顺序的复杂度;
建议
不要用那些需要静态初始化的全局对象;
局部静态对象
此类对象的构造和析构只能执行一次,即使它所在的函数被调用多次;
编译器将会无条件起始时构造出对象,会导致没有被调用的函数内部的静态对象也被初始化;
如何才能让使用该函数时,才会将此类对象构造呢
const Matrix& identity() {
static Matrix mat_identity;
return mat_identity;
}
【cfont】:
先导入一个临时性对象来保护mat_identity的初始化操作;第一次处理identity()时,该临时对象被设置为false,故构造被调用
在将临时对象该为true;而析构根据是否被构造(临时对象为true)即可判断;
而由于该mat_identity是local,无法在静态的内存释放函数中存取它,其地址在downstream component中将会被转换到程序用
来放置全局对象的数据段;
static struct Matrix *__0__F3=0;
struct Martix* identity__Fv() {
static struct Matrix __1mat_identity;
__0__F3 ? 0 : (__ct__6MatrixFv(&__1mat_identity), (__0__F3 = (&__1mat_identity)));
}
析构在于程序文件有关的静态内存释放函数中调用:
char __std_stat_0_c_j() {
__0__F3 ? __dt__6MatrixFv(__0__F3, 2) : 0;
}
上述指针的使用是cfront特有的,条件式析构时所有编译器都需要的;
对象数组
class Point {
public:
Point();
~Point();
};
Point knots[10];
在cfront中,使用vec_new()函数产生以class obj构造而成的数组,而其他较新的编译器将提供两个函数分别处理`没有虚基类
的类`和`内含虚基类的类`;
在Sun中,new出来的数使用_vector_new2(),而不是new的使用_vector_con(),他们都各自拥有一个虚基类函数实例;
void* vec_new(void *array, size_t elem_size, int elem_count,
void (*constructor)(void*), void(*destructor)(void*, char))
当数组Point knots[10];创建时,将会调用10次constructor作用于elem_count个对象上,当离开时,将会调用其destructor;
Point knots[10] = {Point(),-1.0};
那么获得初值的元素,不需要vec_new();
==> vec_new(&knots + 2, sizeof(Point), 8, &Point::Point, &Point::~Point);
默认构造和数组
在程序中,我们无法对构造函数取其地址,编译器中可以;
cfont2.0后才支持,有构造函数的类能生成数组;
那么cfont2.0是怎么做的呢?
根据上述代码中
Point knots[10] = {Point(),-1.0};
编译器需要这样调用
vec_new(&knots + 2, sizeof(Point), 8, &Point::Point, &Point::~Point);
cfont在内部产生一个stub construct,没有参数;用于调用程序员提供的构造,并将默认参数值限制地指定过去(但由此不能
成为内联);
Point::Point() {
Point();
}
只有当类对象数组真正被产生时,stub实例才会被产生以及被使用;
new和delete运算符
Point *pi = new Point();
delete pi;
if(pi != 0) {
Point::~Point(pi);
__delete(p1);
}
当使用delete释放一个对象,编译器会在此前做出一个判断用于保护该对象;
pi所指对象的生命会因delete而结束,故一般考虑将其继续使用;
【注意】但是pi仍然可以当作指针继续使用(仍然指向合法空间),只是存储其中的对象不在合法;
new T[0];
每次new会传回一个独一无二的指针,指向默认为1byte的内存区块
针对数组的new语意
Point* p = new Point[5];
当一个类有构造和析构时,一般vec_new()会被调用;
在C++2.0之前,程序员在使用delete数组时,需要使提供大小,由于需要针对所指的内存空间,及其个数;
而后该使用大小由编译器来处理;
编译器如何记录元素个数呢?
为vec_new()所传回的每一个内存区块配置一个额外的word,记录元素个数;
typedef void *PV;
extern int __insert_new_array(PV array_key, int elem_count);
extern int __remove_old_array(PV array_key);
PV __vec_new(PV ptr_array, int elem_count, int size, PV construct) {
int alloc = 0;
int array_sz = elem_count * size;
if(alloc = ptr_array == 0) {
ptr_array = PV(new char[array_sz]);
}
if(ptr_array == 0) return 0;
int status = __insert_new_array(ptr_array, elem_count);
if(status == -1) {
if(alloc) delete ptr_array;
return 0
}
if(construct) {
register char* elem = (char*)ptr_array;
register char* lim = elem + array_sz;
register PF fp = PF(construct);
while(elem < lim) {
(*fp)((void*)elem);
elem += size;
}
}
return PV(ptr_array);
}
当出现虚拟机制中析构的调用并不是我们想要的
若使用基类通过指针来接收子类创建的数组,传递过去的对象大小不一致,导致执行错误的析构;
应该避免一个基类指针指向一个子类对象所组成的数组;
Placement Operator new的语意
重载定义好的new,它的第二参数而void*;该参数用来存放内存区块,放置新产生的对象;
在已存在的对象上构造新的对象,会发生什么?
若在Placement new在一个已存在的对象上构造新对象,则原对象的析构将不会被调用;如若需要,则要手动调用析构;
我们如何知道该内存区块释放需要先析构才能使用
语言层面上对此并没有做出回答,只是必须指向相同类型的class;
Placement new不支持多态
临时性对象
当使用
c = a + b;
==>
T temp;
temp.operator+(a, b);
c.operator=(temp);
temp.T::~T();
完整表达式
完整表达式的析构应时求值过程的最后一个步骤
((objA > 1024) && (objB > 1024)) ? objA + objB : foo(objA, objB);
临时对象在完整表达式尚未被评估完全之前,不得被摧毁;
|