《深度探索C++对象模型》(二)C++,new,delete,构造/析构,临时对象

来源:互联网 发布:花生壳 80端口 编辑:程序博客网 时间:2024/06/06 13:15

欢迎查看系列博客:

《深度探索C++对象模型》(一)对象模型、存储形式;默认构造函数一定会构造么
《深度探索C++对象模型》(二)C++,new,delete,构造/析构,临时对象(本篇)
《深度探索C++对象模型》(三)构造函数、拷贝构造和初始化列表

--------------------------------------------------------------------------------------------------------------

即使父类的析构函数设置为虚函数,那么当父类指针指向子类对象的时候,也有不能够正确析构的情况。比如

Point* ptr = new Point3d[10];

六、执行期语意学

本章三个知识点比较重要:

1. 对象的构造和析构

2. new和delete运算符 

3. 临时变量。

第一、构造和析构函数

---》C++支持栈上的对象,所以栈上的变量的constructor和destructor的安插就要注意了,比如swith-case语句,在每个case的后面都要进行constructor或者destructor

---》全局变量,局部静态变量和对象数组。(p250)

    对象数组的初始化。例如后面的声明 Point knots[10];如果Point没有constructor和destructor,我们只需要在内存开辟10*Point个大小的空间即可,如果Point有constructor或者destructor那么,这个cosntructor和destructor必须轮流实施与每一个元素之上,这个工作是由runtime library函数完成。cfront中使用vec_new()函数产生出以class object构造而成的数组,下面是vec_new的原型:

    void* vec_new (                   void *array,                     //数组起始位置                   size_t elem_size,                //class object的大小                   int elem_count,                  //数组中元素个数                   void (*constructor)(void*),      //构造函数指针                   void (*destructor)(void*,char)   //析构函数指针                   )

那么,针对下面代码

Point knots[10]

的调用如下

vec_new(&knots, sizeof(Point), 10, &Point::Point,0)
下面的定义,带有一部分初始化,结果就是 前三个元素使用Point的构造函数,后面7个仍然使用ver_new

Point knots[10]={Point (),Point(1.0,1.0,0.5),-1.0}
---》如果一个class的构造函数带有默认参数,怎么办。p(252)

    例如:class complex{               complex(double=0.0,double =0.0) } ,怎么生命 complex的对象数组。

这种情况下,编译器的做法是:最终还是要调用vec_new这个函数,但是编译器会生成一个sub constructor来调用带参数的构造函数。

第二、new 和 delete 运算符。

---》都是从c语言的malloc和free为标准完成的。p(254)

---》如果int* p_array = new int [ 5 ];vec_new函数不会被调用,反而是new运算符会被调用:p(257)

int* p_array = (int*) __new(5*sizeof(int)); 
如果下面的代码:

struct simple_aggr{float f1,f2;};simple_aggr *p_aggr = new simple_aggr[5]

第一种情况:vec_new()也不会被调用,因为simple_aggr没有定义一个构造函数和析构函数,所以这个操作只是单纯地获得内存和释放内存而已。让new运算符来完成就足够了。

第二种情况:如果simple_aggr定义了构造函数vec_new还是会被调用,但是这次vec_new是返回堆上的空间,vec_new根据第一个参数来判断是从栈上分配还是从堆上分配。

总结:程序员使用了new运算符,可能底层根本没用到__new运算符,而是vec_new,也许这就是new存在的意义吧。

---》delete[ ]运算符p(259)

[ ]存在的意义就是告诉编译器,去寻找数组的维度。编译器如何去计算维度?解决之道就是在vec_new()所传回的每一个内存区块配置一个额外的word,然后把元素数目藏在这个word之中。

---》下面列出vec_new的源文件p(261)

PV __vec_new(PV ptr_array,int elem_count, int size, PV constructor){    //如果ptr_array是0,从堆中配置数组    //如果不是0,表示程序员写的是 T array[count] 或new (ptr_array)T[10];    int alloc = 0;//我们要在vec_new中配置吗    int array_sz = elem_count*size;    if(alloc = ptr_array ==0)        ptr_array = PV(new char[array_sz]);//全局运算符new    //在exception handling之下,将丢出exception bad_alloc    if(ptr_array == 0)        return 0;    //把数组元素数目放到cache中    int status = __insert_new_array(ptr_array,elem_count);    if(status == -1)    {        //在exception handling 之下将丢出exception,将丢出exception bad_alloc        if(alloc) delete ptr_array;        return 0;    }    if(construct)    {        register char* elem = (char*)ptr_array;        register char* lim    = elem+array_sz;        //PF是一个typedef,掉膘一个函数指针        register PF fp = PF(constructor);        while(elem<lim)        {            //通过fp调用constructor作用域“this”元素上(由elem指出)            (*fp)((void*)elem);            //前进下一个元素            elem += size;        }     }    return PV(ptr_array);}
--->vec_delete()的操作差不多,但是行为并不总是C++程序员所预期或需求的。例如下面的情况p(262)
class Point{                          class Point3d:public Point {public:                               public:    Point();                                Point3d();    virtual ~Point();                       virtual ~Point3d();}                                                }
如果执行下面的程序。
//这完全不是个好主意。Point* ptr = new Point3d[10];//这并不是我们想要的,只有Point::~Point会被调用。delete [] ptr;
那么语言层面很难解决,只能程序员自己通过遍历解决了
for( int ix =0;ix<elem_count;++ix){    Point3d* p = &((Point3d*)ptr)[ix];    delete p;}
---》Placement Opertator new

有一个预选定义好的new的重载placement Operator new,调用方法如下:

Point2w *ptw = new(arena) Point2w;

其中arena是指向内存中的一个区块,用于放置新产生出来的Point2w。具体详细还是看书吧,很难懂。

第三、临时对象

---》临时性对象,是神秘的,诡异的。而且临时性对象的产生与否部分情况取决于编译器的优化。

(0) T a, b, d;

(1) T c = a+b; //可能不会产生临时性对象

(2)  d = a+b;   //可能产生

(3)  a+b;           //必须有临时对象产生。

---》临时性对象的生命周期

    比如:a和b都是string类型变量,printf("%s\n",a+b);这句话要产生一个string 类型的临时变量。那么临时对象的生命期是什么呢?在Standard C++之前临时对象的生命期并没有明确的指定,而是有编译厂商自行决定的。

    C++ standard之后规定:临时对象在完整表达式尚未评估完全之前不得摧毁。

---》临时对象的生命周期意外情况

1.表达式被用来初始化一个object,例如: progName + progVersion都是String类型

bool verbose;String progNameVersion = !verbose ? 0 : progName+progVersion;

假如verbose =ture ,三目运算法结束后,将临时对象(temp)销毁,那么progNameVersion将得不到正确的值,所以这种情况必须等到object初始化完成之后再销毁temp。

即便是这样,有些情况还是不能保证程序的正确:

const char *NameVersionChar = progName + progVersion;//经过编译器之后(C++ pseudo code)String temp;operator+(temp, progName, progVersion);NameVersionChar = temp.String::opertor char*();temp.String::~String();

此时NameVersionChar指向定义的堆内存

</pre><p></p><p>2.当一个临时性对象被一个reference绑定。例如:</p><p><pre name="code" class="cpp">const String &space = " ";//那么编译器产生下面的代码(C++ pseudo Code)String temp;temp.String::String(" ");const String &space = temp;

很明显,如果这时候temp被析构,那么reerence 将无用了,所以:如果一个临时对象被绑定与一个reference,对象将残留,直到被初始化之reference的生命结束或者知道临时对象的生命范畴(scope)结束在析构,销毁。

---》临时对象的产生和销毁是否影响效率

(笔者还没有完全理解原书中的意思,还需要时间来理解)

原创粉丝点击