美文网首页
《深入探索C++对象模型》笔记 Chapter2 构造函数

《深入探索C++对象模型》笔记 Chapter2 构造函数

作者: 朱明代月 | 来源:发表于2018-08-19 19:57 被阅读14次

    《深入探索C++对象模型》笔记 Chapter2 构造函数
    《深入探索C++对象模型》笔记 Chapter3 成员变量
    《深入探索C++对象模型》笔记 Chapter4 成员函数

    第2章 构造函数

    2.1 默认构造函数

    之前以为没有声明构造函数,编译器就会自动生成一个默认构造函数,然而事实上并不是这样。默认构造函数分为 trival 和 nontrival ,对于无关紧要的默认构造函数,编译器不会生成。(这里多说一句,一个类的一些构造函数以及析构函数是否 trival 等特性,可以用 type_traits 提取出来,在模板编程中十分有用,比如删除一个区间的元素,如果析构函数是 trival ,那么就不需要一个个去调用析构函数。以上内容《STL源码剖析》有详细解释)。

    nontrival 默认构造函数又分为以下几种情况:

    • 成员变量有默认构造函数
      即使用户自己实现了构造函数,也要扩张构造函数,在用户代码执行之前,调用必需的默认构造函数。
    • 继承的基类有默认构造函数
    • 声明了虚函数
      在默认构造函数中初始化 vptr
    • 虚继承

    2.2 拷贝构造函数

    拷贝分为两种,memberwise copybitwise copy,前者是深拷贝,会递归拷贝成员变量,后者是浅拷贝,将内存中的每个字节拷贝过去。

    诸如A a1;A a2=a1;这样的代码,即使A没有声明拷贝构造函数,编译器也可以完成上述的行为,依赖的就是memberwise copybitwise copy

    同默认构造函数一样,并不是没有声明拷贝构造函数而编译器需要时就会生成一个,只有判断 nontrival 才会生成。而是否 nontrival 取决于一个类是不是符合 bitwise copy 语义。判断方式和2.1列的几点类似。

    2.3节有一句话说的很好,判断是否 trival 关键在于 “class 是否含有编译器内部产生的 member”。例如有虚函数,编译器会给class 添加一个 vptr 成员,此时默认构造函数和拷贝构造函数都是 nontrival 了,因为编译器要做 vptr 的设置和修改。

    以及,如果成员都是POD类型, 那么没有必要自己声明拷贝构造函数,因为编译器做的已经是最优了。

    2.3 NRV优化

    Named Return Value 优化就是说一个函数生成并返回一个临时对象,需要调用构造函数,拷贝构造函数,临时对象的析构函数,这个过程效率很低下,编译器可以做优化。步骤如下:

    1. 函数添加一个参数,为欲返回对象的引用
    2. 在调用函数前,先创建该对象
    3. 进入函数后,就可以直接对这个对象的引用进行操作
    4. 返回值就可以为空了

    于是 A a = fun(); 相当于A a; fun(a); 只有一次构造函数的消耗。可以写个demo验证一下:

    #include <iostream>
    using namespace std;
    class B{
        public:
                B(){
                    cout<< "this is constructor without parameter of B" <<endl;
                }
                B(int num):k(num){
                    cout<< "this is constructor with parameter of B" <<endl;
                }
                B(const B& b){
                    k=b.k;
                    cout<< "this is copy constructor of B"<<endl;
                }
                B& operator=(const B& b){
                    k=b.k;
                    cout << "this is operator= of B" <<endl;
                    return *this;
                }
                ~B(){
                    cout<< "this is destroyor of B"<<endl;
                }
        private:
                int k;
    
    };
    B func(){
        B b;
        return b;   
    }
    int main(){
        cout<< "result of func(b)-----------"<<endl;
        B b=func();
        return 0;
    }
    

    打印结果为

    result of func(b)-----------
    this is constructor without parameter of B
    this is destroyor of B
    

    2.4 初始化成员列表

    必须使用 member initialization list 的情况:

    • 成员是引用
    • 成员是 const member
    • 基类的构造函数有一组参数
    • 成员变量的构造函数有一组参数

    如果不使用 member initialization list ,而是在构造函数的函数体内进行赋值操作,那么编译器可能会产生一个临时对象,再调用赋值操作符拷贝临时对象,最后再将临时对象删除,效率会十分低下。

    这里可以继续前面的demo验证一下:

    #include <iostream>
    using namespace std;
    class B{
        public:
                B(){
                    cout<< "this is constructor without parameter of B" <<endl;
                }
                B(int num):k(num){
                    cout<< "this is constructor with parameter of B" <<endl;
                }
                B(const B& b){
                    k=b.k;
                    cout<< "this is copy constructor of B"<<endl;
                }
                B& operator=(const B& b){
                    k=b.k;
                    cout << "this is operator= of B" <<endl;
                    return *this;
                }
                ~B(){
                    cout<< "this is destroyor of B"<<endl;
                }
        private:
                int k;
    
    };
    class A{
        public:
                A(int num):k(num){}
                A(){
                    k=2;
                }
        private:
                B k;
    
    };
    int main(){
        cout<< "result of A(1)--------------"<<endl;
        A* a1=new A(1);
        delete a1;
        cout<< "result of A()---------------"<<endl;
        A* a2=new A();
        delete a2;
        return 0;
    
    }
    

    输出结果为:

    result of A(1)--------------
    this is constructor with parameter of B
    this is destroyor of B
    result of A()---------------
    this is constructor without parameter of B
    this is constructor with parameter of B
    this is operator= of B
    this is destroyor of B
    this is destroyor of B
    

    其中,带参的构造函数使用了初始化成员列表,无参的构造函数采取了函数体内赋值,打印结果说明后者多出了临时对象的构造、拷贝和析构等步骤。

    另外需注意,初始化顺序按照成员的声明顺序。

    总结

    不知不觉间,编译器已经帮你做到了最好。

    相关文章

      网友评论

          本文标题:《深入探索C++对象模型》笔记 Chapter2 构造函数

          本文链接:https://www.haomeiwen.com/subject/ktyciftx.html