zoukankan      html  css  js  c++  java
  • 第2章 构造函数语意学

    2.1 默认构造函数的构建操作

     关于C++的默认构造函数的构建是在被需要的时候被编译器产生出来的,关于其中的"被需要的时候"指的是被编译器需要的时候,不包括被程序员需要的时候。其中被编译器需要的时候大致包括以下四种:

      1. 该类中含有类对象成员(该成员含有默认构造函数)。

      2. 该类的基类中含有默认构造函数

      3. 该类中含有虚函数

      4. 该类在继承体系中存在一个或多个的虚基类。

    注: 在添加默认构造函数时,如果该类中含有其他构造函数,这些构造函数将会被扩充如:

    class B{
        public:
            B(): B_data_member(0){}
        private:
            B_data_member;
    }
    
    
    class A{
        public:
            A(int i = 0){
                A_data_member = i;
            }
        
        private:
            
            B A_data_member;
    }
    
    在其进行初始化时会进行下面的操作
        A(){
            A_data_member.B::B();
        }
        A(int i = 0){
            A_data_member.B::B();
            A_data_member = i;
        }

    当然在有基类的构造函数进行填充时,基类默认构造函数在其类成员默认构造函数之前,即:基类默认构造函数--->类成员默认构造函数--->该类被执行构造函数内部的相关成员变量的初始化。 

    2.2 拷贝构造函数的构建操作

    将一个对象的内容传给另一个对象,一般大约有三种情况如:赋值,传参,调用函数返回接收。

    在对象的初始化过程中,如果以一个对象作为另一个对象的初值时,关于其Data Member将会从一个对象直接赋值到另一个对象中,但是member class object 将会采用递归调用的方式进行赋值,

    class A{
        public:
            ...
        private:
            int cnt;
            char *str;
    }
    
    在进行赋值时会发生
        A a;
        A b = a;
    则会以
        b.cnt = a.cnt;
        b.str = a.str;
    的形式进行复制。

    关于非Bitwise Copy Semantics(位逐次拷贝)的情况有一下四种:

    1. 当一个类中含有另一个类的成员时,且其自身含有一个Copy Constructor(包括程序员自身写的或者由编译器进行合成的)。

    2. 该类的基类中含有一个Copy Construcor。

    3. 该类中含有Virtual Function。

    4. 在继承的体系链中含有Virtual Base Class。

    注:与默认构造函数的生成相同。

    class A{
        public:
            A(){}
            ~A(){}
            virtual void draw();
            virtual void run();
        private:
            ...
    }
    
    class B: public A{
        public:
            B(){}
            ~B(){}
            void draw();
            void run();
        private:
            ...
    }
    
    
    B b;
    A a = b;

    在最后的赋值中,a的赋值如下图

    其中a得到的时A类中的virtual table的地址,但是当采用指针或者引用的方式的时候a才将会只想B中的virtual table,如: A *a = b;

     2.3 程序转化语意学

    明确的初始化操作

    1. 这种初始化的过程大致分为两大类。

      1.重写每一个定义,其中的初始化操作将被删除。

      2.类的拷贝构造函数将被添加进去。

    T t0;
    
    void foo(){
        T t1(t0);
        T t2 = t0;
        T t3 = T(t0);
    }
    
    
    针对以上t1, t2, t3的将被转化为一下操作
    
    void foo(){
        T t1;
        T t2;
        T t3;
        
        
        t1.T::T(t0);
        t2.T::T(t0);
        t3.T::T(t0);
    }

    2. 参数的初始化

    void foo(T t);
    
    
    T t1;
    foo(t1);
    
    该操作将被转化为
    T __Temp; //临时对象
    __Temp.T::T(t1);
    foo(__Temp);
    
    上述过程为临时对象通过T的拷贝构造函数进行初始化,然后通过位逐位拷贝的形式传给局部参数t。
    对于该转化的说明还有一部分,但是目前不是很清晰原因。

      3. 返回值的初始化

    关于返回值的初始化问题大致分为两步

      1. 首先加上一个额外参数,类型是该类型的返回类型的一个引用,用来放置被"拷贝构建"而得的返回值。

      2. 在函数的return之前安插一个拷贝构造函数调用操作,以便将欲传回的对象的内容当做上述新增参数的初值。

    定义如下
    T foo(){
        T t;
        // 相关操作
        return t;
    }
    
    
    该操作将被转化为
    void foo(T& __result){
        T t;
        t.T::T();
        __result.T::T(t);
        return;
    }
    
    至于foo().member_function()的调用则会转化为
    T __Temp;
    (foo(__Temp), __Temp).member_function();的操作

    4. 在使用者层面做的优化

    定义如下
    T foo(const P &p1, const P &p2){
        T t;
        // 用p1,p2进行t的相关操作
        return t;
    }
    
    在这个定义中会出现逐成员的拷贝到编译器所产生的__result之中,可以在T的内部定义一个构造函数可以直接计算t的值,如:
    T foo(const P &p1, const P &p2){
        return T(p1, p2);
    }
    经这个转化后,比以前的效率要调出一部分。

     5. 程序在编译器方面的优化

    在能够对一个类进行bitwise方式的初始化时,编译器会在类中进行调用memcpy(this, 0, sizeof(class_name)), 或者进行memset()的操作。如:

    class T{
        friend T foo(double);
        public:
            T(){
                memcpy(this, 0, sizeof(T));
            }
        private:
            double array[100];
    }

      如果一个类中有以下四种情况时:

    1. 含有一个类成员(其含有默认的构造函数时)。

    2. 其base class中含有默认的构造函数时。

    3. 含有virtual function时

    4. 在继承的体系中含有一个或多个的virtual class时。

    仍以bitwise的方式进行初始化会发生错误。如:

    class T{
        public:
            T(){
                memcpy(this, 0, sizeof(T));
            }
            
            virtual ~T();
            //...
    }
    其中的T::T()会被改写为:
    T::T(){
        __vptr_T = __vtb1_T;
        memcpy(this, 0, sizeof(T));
    }
    进而导致__vptr_T被再次进行赋值为0,而丢失它的virtual table

     2.4 成员们的初始化队伍

    成员初始化时必须要用到初始化列表的情况有:

    1.初始化一个引用类型的对象时。

    2.初始化一个const 成员时。

    3.调用一个基类的构造函数,而且该构造函数具有一组参数时。

    4.调用一个类成员的构造函数时,而且该后早函数具有一组参数时。

    在类的初始化构造过程中也会发生一些改写。

    class T{
        private:
            String _name;
            int _cnt;
        public:
            T(){
                _name = 0;
                _cnt = 0;
            }
    };
    
    则被改写为
    
    class T{
        private:
            String _name;
            int _cnt;
        public:
            T(){
                _name.String::String();
                String tmp = String(0);
                _name.String::operator=(tmp);
                tmp.String::~String();
                _cnt = 0;
            }
    };
    
    在应用初始化列表时
    
    class T{
        private:
            String _name;
            int _cnt;
        public:
            T(): _name(0){
                _cnt = 0;
            }
    };
    
    改写为
    
    class T{
        private:
            String _name;
            int _cnt;
        public:
            T(){
                _name.String::String(0);
                _cnt = 0;
            }
    };

      注:编译器拓展的代码会出现在程序员自己写的代码之前,如:_cnt的赋值之前。

    在使用初始化列表进行初始化时注意变量在类内部的声明顺序。

    class T{
        private:
            int i;
            int j;
        public:
            T(int val): j(val), i(j) //这里会警告
            {}
    };
    
    因为在初始化的过程中先初始化i被初始化为未经初始化的j,而发出警告。
    
    class T{
        private:
            int i;
            int j;
        public:
            T(int val): i(val), j(i)
            {}
    };        

      在初始化的过程中也可以调用drived class member function,其返回值可以作为基类构造函数的一个参数

    class T: public B{
        private:
            int _fval;
        public:
            int fval(){return _fval}
            T(int val): _fval(val), B(fval){}
    };
    
    其中的构造函数会被改写为:
    T::T(int val){
        ...
        B::B(this, this->_fval);
        _fval = val;
    }

     

  • 相关阅读:
    存储过程3前台
    最简单Login程序
    存储过程前台2
    程序员 开发工具箱
    存储过程4前台
    存储过程 insert
    公司网络解决方案
    存储过程前台
    linux常用指令
    ReentrantLock源码解析3优先响应中断的lockInterruptibly
  • 原文地址:https://www.cnblogs.com/hebust-fengyu/p/10478817.html
Copyright © 2011-2022 走看看