title: “ 《深度探索C++对象模型》笔记(6)\t\t”
tags:

  • 笔记
    url: 1454.html
    id: 1454
    categories:
  • C/C++
    date: 2018-12-30 15:46:42

将object尽可能放置在使用它的那个程序区段附近,这样可以节省非必要的对象的构造和析构成本。少用点return,也可以减少编译后的代码量,看了这张更加深刻的理解了一些编程规范里一些要求的深意。 刚知道还能重载new操作符。。。真的c++太能玩了

第六章 执行期语意

对象的构造与析构

全局对象

已经初始化全局对象均存储在data segment(数据段),未初始化的全局变量存储在BSS(block started by symbol),C++中所有全局变量均会进行静态初始化和内存释放操作,对于显示的给定一个初始值时静态初始化过程会将其作为初值,否则会将内存内容置为0. cfront提供一个成本较高的静态初始化方法,munch方法:(用nm命令获取所有的object file,并搜索sti和std来找到需要被调用的文件用于第三步)

  1. 为每一个需要静态初始化的文件产生一个__sti()函数(static initialization),内含必要的constructor调用操作或者inline expansions。
  2. 为每一个需要静态的内存释放操作产生__std()函数(static deallocation),内含必要的constructor调用操作或者inline expansions。
  3. 提供一组running library”munch”函数:_main()函数调用所有的sti(),一个exit()函数调用std()函数。

局部静态变量

局部的也要保证一次构造、一次释放,还要在使用时第一次构造而不是启动程序是全部构造,所以增加了标识符,bool类型来指定是否已经构造过。

对象数组

void vec_new(
void array, //数组的起始地址
size_t elem_size, //数组中每一个元素的大小
int elem_count, //数组中元素的个数
void (
constructor)(void), //形参个数为0的默认构造函数指针
void (
destructor)(void*, char) //析构函数指针
)

void vec_delete(
void array, //数组的起始地址
size_t elem_size, //数组中每一个元素的大小
int elem_count, //数组中元素的个数
void (
destructor)(void*, char) //析构函数指针
}

编译器通过运行时库函数进行对数组中每个对象进行初始化、释放操作。 对于提供了部分初始化列表的对象数组:

Point a[10]={Point(),Point(1.0,1.0),-1.0};
Point::Point (&a[0]);
Point::Point (&a[1],1.0,1.0);
Point::Point (&a[2],1.0,0.0);
vec_new(&a+3,sizeof(Point),7,Point::Point,0);

?为什么vec_new要传入析构函数的指针?难道是vec_new与vec_delete内部共同维护了一个链表,在new的时候会吧涉及到(具有)析构函数的,将函数指针存起来,等最后的时候delete直接调用这个链表中的所有的指针? 后面提到了vec_new有责任在个别数组元素构造过程中发生exception时将内存释放掉。

默认构造函数和数组

当自己定义的构造函数有参数时,构建对象数组: Pointx b[10];此时因为vec_new只能自动调用无参的构造函数(默认构造函数),因此需要做改变,建立默认构造函数,并在初始化列表中调用自定义构造函数: Pointx::Pointx() : Pointx(0.0,0.0) {} 此步骤会被编译器自动生成,对应的参数内容都会以内存位0的默认值作为参数。这个stub constructor是内部的,只有在objects数组真正被产生出来时,此函数实例才会被产生并使用,不会影响无初始化列表的默认构造函数。

new/delete

new通过malloc完成,delete通过free实现 对于内置类型,先创建内存后赋值:

int pi = new int(5);
//int
pi;
//if(pi = new(sizeof(int)))
//??? *pi = 5;
delete pi;
//if(pi != 0)
delete(pi)

对于class

Point3d *origin = new Point3d;
if(origin = new(sizeof(Point3d))) {
try {
origin->Point3d::Point3d(origin);
}
catch(…) {
delete(origin)
throw;
}
}//如果构造过程发生异常,会先释放内存然后上抛异常
delete origin;
if(origin != 0) {
Point3d::~Point3d(origin);
__delete(origin);
}

对于数组new

对于内置类型或无构造函数和析构函数的类型,用__new构建,大小为n*sizeof(type)方式构建内存。如果有构造会调用vec_new 对于delete只需要输入delete [] xx;数组大小会自动计算不用填充[],若不写[]会只释放第一个。

Placement Operator new

void operator new(size_t, void p) { return p; } 调用:Point2w ptw2 = new(arena) Point2w;与Point2w ptw1 = (Point2w*) arena; 区别 前者等于:

Point2w ptw2 = (Point2w) arena;
ptw2->~Point2w();
if(ptw2 != 0) ptw2->Point2w::Point2w();

一般不要重载new用于多态,避免派生类大小不可控

临时性对象Temporary objectes

T c=a+b,编译器可能在a+b后将结果赋予临时对象,后再将结果复制到c(拷贝构造、赋值操作符重载) 临时对象的被摧毁,应该是对完整表达式(full-expression)求值过程中的最后一个步骤。 凡是有表达式执行结果的临时性对象,应该保存到object的初始化操作完成为止。 string proNameVersion = !verbose ? 0 : proName + progVersion;proNameVersionobjecrt初始化需要用到该临时对象,因此应该保存到object的初始化操作完成为止。 如果一个临时性对象绑定于一个reference,对象将残留,直到被初始化之reference的生命结束,或者知道临时对象的生命范畴结束,视哪种情况先到达而定。