• 【C++对象模型】第六章 执行期语意学


      执行期语意学,即在程序执行时,编译器产生额外的指令调用,确保对象的构造,内存的释放,以及类型转换与临时对象的生成的安全进行。

    1、对象的构造和析构

      对于类对象的构造,一般在定义之后则开始内部的构造过程。一个区段可能有多个return离开点,所以可能导致一些对象创建后,没有用就需要释放,这就造成了很多浪费。所以建议把object放置在使用它的区段的附近。

      而对于已构造的对象,其析构函数必须放在每一个离开点(当object还存在)之前。

    2、全局对象

      对于全局变量,C++会保证在调用全局变量之前,将全局变量构造出来。

      而所有的全局变量都被放置在程序的数据段中(data segment),并且为没有显示指定值的变量初始化为0.

      同时对于全局的类对象,在编译时期被放置于data segment中并且内容也为0.只有在程序启动时其对应的constructor才会实施。因此object需要静态初始化。

      静态初始化的munch策略:

    • 为每个需要静态初始化的文件产生一个_sti()函数,内含必要的constructor调用操作和inline expansions。
    • 为每一个需要静态的内存释放操作的文件,产生一个_std()函数。
    • 提供一个_main()函数调用所有的_sti()函数,一个_exit()函数调用所有的_std()函数

      

    3、局部静态对象 

      局部静态对象的constructor只能被施行一次,destructor也只有一次。

      所以对于局部静态对象,一个很简单的思路就是导入一个临时性对象,当第一次传入时,设置为true,之后则不再进行处理。

        void Point& identity()   
        {  
            staitc Point sta_Point;  
            return sta_Point;  
        }  

      不管该函数被调用多少次,静态局部对象sta_Point只会被初始化一次,理所当然也只会被析构一次,这种通过函数返回局部静态对象的方法通常是用来解决跨模块(文件)的全局变量的初始化顺序问题。

    4、对象数组

      对于以下数组定义:

        Point knots[ 10 ]; 

      如果 Point未定义默认构造函数或者析构函数,那么产生该数组不会比产生内建类型的数组做的更多,只需要配置足够的内存以存储10个连续的Point元素。
      如果Point明确定义了默认构造函数,那么构造函数必须轮流施行于每一个元素上。

      我们一般使用vec_new()函数来构造数组(这是在cfront中的方式), 对于VS等,则提供一个用来处理没有"virtual base class"的class,另一个用来处理"内含virtual base class"的class。后一个函数通常称为 vec_vnew()。函数的原型一般如下:

        void *  
        vec_new(   
            void *array,                       // 数组的起始地址  
            size_t elem_size,                  //每一个class object大小  
            int elem_count,                    //数组中的元素个数  
            void (*constructor)(void *)        //类的constructor函数指针  
            void (*destructor)(void *, char)   //类的destructor函数指针  

      所以对上述的Point对象的初始化,对应的vec_new()的实例化如下:

        vec_new( &knots, sizeof(Point), 10, &Point::Point, 0);

      同理,对于对象的删除,也有类似的vec_delete()来进行操作。

        void *  
        vec_new(   
            void *array,                       // 数组的起始地址  
            size_t elem_size,                  //每一个class object大小  
            int elem_count,                    //数组中的元素个数  
            void (*destructor)(void *, char)   //类的destructor函数指针 

    5、new和delete运算符

        int *pi = new int(5);

      其实是通过两个步骤完成的,第一步通过适当的new运算符函数实例,配置所需的内存,然后将配置来的内存设定初值。

      使用pi,和使用pi所指的对象,其差别在于哪一个生命已经结束了。因为即使对象不合法,但是指针所指的对象也是合法的。

      new其实是通过标准的c malloc完成的,每一次对new的调用必须传回一个独一无二的指针,指向默认1byte的内存地址。

      如果类对象数组没定义constructor和destructor,则不会调用vec_new。

      对于delete[] p_array, 只有中括号出现时,编译器才会寻找数组的维度,否则他只会假设单独的一个object要被删除。

      那么如何记录数组的元素个数呢?一个明显的方法是为vec_new()所传回的每一个内存区域块设置一个额外的word,然后把元素个数包藏在那个word之中。而包藏的数值通常称为cookie。

      在原始编译器中,有两个主要函数用来存取cookie

      注意,避免一个base class指针指向一个derived class object所组成的数组。即:

        Point *ptr = new Point3d[10];

      如果一定要这样做,默认情况下只会交给施行vec_delete()函数的”被删除之指针类型的destructor“,也就是Point destructor。

      所以,需要程序员如下显著的进行释放内存。

        for(int ix = 0; ix < elem_count; ++ ix) {  
            Point3d *p = &((Point3d*)ptr)[ix];  
            delete p;  
        }  

    6、临时性对象

      对于临时性对象,许多编译器会产生临时性对象进行临时结果的存储。而临时对象的生命规则是需要的时候它应该存在,不需要了应该尽快销毁。所以就引出了临时性对象的生命规则的两个例外:

      1、表达式用来初始化一个对象,只有对象的初始化完成了临时对象(用来存储表达式结果)才可以被销毁。

      2、一个临时对象被绑定于一个reference,对象将残留,直到reference生命结束才可以,因为他是一个别名,否则reference没有意义了。Eg:const String &space=” ”,编译器将会产生以下代码:String  temp;temp.String::String(“ ”);constString &space=temp。这个temp就是一个临时对象

      临时对象的存在,是由于编译器产生的,可能会使代码的效率较低。但通过将个别成员,变量放置在register(寄存器)中、或者通过反聚合方法来提高效率

  • 相关阅读:
    题解 [CF891C] Envy
    题解 [BZOJ4710] 分特产
    题解 [BZOJ2159] Crash的文明世界
    题解 [BZOJ4144] Petrol
    #leetcode刷题之路1-两数之和
    week 7 文件操作与模板
    coursera 北京大学 程序设计与算法 专项课程 STL week8 list
    coursera 北京大学 程序设计与算法 专项课程 完美覆盖
    JSTL标签库不起作用的解决方案 .(转)
    javax.servlet.jsp.PageContext.getELContext()Ljavax/el/ELContext解决办法(转)
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/6754326.html
Copyright © 2020-2023  润新知