• c++类的构造析构顺序


    1 前言

    程序的正确运行依赖于对变量生命周期的管理,但是类的构造和析构顺序有时非常隐蔽,控制不好可能会引发不可预知的错误,所以本文探讨一下c++类的构造和析构顺序。

    2 构造析构顺序的影响因素

    构造析构顺序主要受类与类之间的关系和类的作用域的影响。

    • 类与类之间的关系
      • 继承关系
        • 单继承
        • 多继承
        • 菱形继承
      • 包含关系/成员变量,比如A是B的成员变量
      • 声明的先后顺序
    • 类的作用域
      • 全局变量
      • 静态变量
      • 局部变量

    结合以上因素,通过设计实验来验证构造析构的顺序,回答以下几个问题:

    • 父类和子类的构造析构顺序
    • 父类和成员变量的声明顺序对构造析构顺序的影响
    • 作用域与构造析构顺序的关系

    3 实验

    3.1 实验设计

    /**
     * 测试构造函数和析构函数的顺序,包含以下6种情况
     *  1. 单继承
     *  2. 多继承
     *  3. 菱形继承
     *  4. 包含成员变量
     *  5. 声明顺序和构造/析构顺序
     *  6. 既有继承又有成员变量
     * */
    
    #include <iostream>
    #include <memory>
    class Base {
    public:
      Base() {
        std::cout << "Base: constructor" << std::endl;
      }
      virtual ~Base() {
        std::cout << "~Base: destructor" << std::endl;
      }
    };
    
    class A : public Base {
    public:
      A() {
        std::cout << "A: constructor" << std::endl;
      }
      virtual ~A() {
        std::cout << "~A: destructor" << std::endl;
      }
    };
    
    class B : public Base {
    public:
      B() {
        std::cout << "B: constructor" << std::endl;
      }
      virtual ~B() {
        std::cout << "~B: destructor" << std::endl;
      }
    };
    
    class AB : public A, public B {
    public:
      AB() {
        std::cout << "AB: constructor" << std::endl;
      }
      virtual ~AB() {
        std::cout << "~AB: destructor" << std::endl;
      }
    };
    
    class BA : public B, public A {
    public:
      BA() {
        std::cout << "BA: constructor" << std::endl;
      }
      virtual ~BA() {
        std::cout << "~BA: destructor" << std::endl;
      }
    };
    
    class MemberA {
    public:
      MemberA() {
        std::cout << "MemberA: constructor" << std::endl;
      }
      virtual ~MemberA() {
        std::cout << "~MemberA: destructor" << std::endl;
      }
    };
    
    class MemberB {
    public:
      MemberB() {
        std::cout << "MemberB: constructor" << std::endl;
      }
      virtual ~MemberB() {
        std::cout << "~MemberB: destructor" << std::endl;
      }
    };
    
    class HasMemberAB {
    public:
      HasMemberAB()
        : mb_(new MemberB()),
          ma_(new MemberA()) {
        std::cout << "HasMemberAB: constructor" << std::endl;
      }
      virtual ~HasMemberAB() {
        std::cout << "~HasMemberAB: destructor" << std::endl;
      }
    private:
      std::shared_ptr<MemberA> ma_;
      std::shared_ptr<MemberB> mb_;
    };
    
    class HasMemberBA {
    public:
      HasMemberBA()
        : ma_(new MemberA()),
          mb_(new MemberB()) {
        std::cout << "HasMemberBA: constructor" << std::endl;
      }
      virtual ~HasMemberBA() {
        std::cout << "~HasMemberBA: destructor" << std::endl;
      }
    private:
      std::shared_ptr<MemberB> mb_;
      std::shared_ptr<MemberA> ma_;
    };
    
    class DerivedAndMember : public A, public B {
    public:
      DerivedAndMember() {
        std::cout << "DerivedAndMember: constructor" << std::endl;
      }
      virtual ~DerivedAndMember() {
        std::cout << "~DerivedAndMember: destructor" << std::endl;
      }
    private:
      MemberA ma_;
      MemberB mb_;
    };
    
    void Test01_SingleDerived() {
      std::cout << "测试01:单继承 {" << std::endl;
      {
        A a;
      }
      std::cout << "}
    
    " << std::endl;
    }
    
    void Test02_MultiDerived() {
      std::cout << "测试02:多继承 {" << std::endl;
      {
        std::cout << "case 1: 先继承A,再继承B" << std::endl;
        AB ab;
      }
      {
        std::cout << "case 2: 先继承B,再继承A" << std::endl;
        BA ba;
      }
      std::cout << "}
    
    " << std::endl;
    }
    
    void Test03_HasMember() {
      std::cout << "测试03:包含成员变量 {" << std::endl;
      {
        std::cout << "case 1: 先声明A,再声明B" << std::endl;
        HasMemberAB hasMemberAB;
      }
      {
        std::cout << "case 2: 先声明B,再声明A" << std::endl;
        HasMemberBA hasMemberBA;
      }
      std::cout << "}
    
    " << std::endl;
    }
    
    void Test04_DerivedAndMember() {
      std::cout << "测试04:既有多继承,又有成员变量 {" << std::endl;
      {
        DerivedAndMember dm;
      }
      std::cout << "}
    
    " << std::endl;
    }
    
    int main() {
      Test01_SingleDerived();
      Test02_MultiDerived();
      Test03_HasMember();
      Test04_DerivedAndMember();
      return 0;
    }
    

    3.2 实验结果

    // output
    测试01:单继承 {
    Base: constructor
    A: constructor
    ~A: destructor
    ~Base: destructor
    }
    /** 
     * 结论:
     *  单继承情况下,
     *  - 构造:先构造父类,再构造子类
     *  - 析构:先析构子类,再析构父类
     */
    
    // output
    测试02:多继承 {
    case 1: 先继承A,再继承B
    Base: constructor
    A: constructor
    Base: constructor
    B: constructor
    AB: constructor
    ~AB: destructor
    ~B: destructor
    ~Base: destructor
    ~A: destructor
    ~Base: destructor
    case 2: 先继承B,再继承A
    Base: constructor
    B: constructor
    Base: constructor
    A: constructor
    BA: constructor
    ~BA: destructor
    ~A: destructor
    ~Base: destructor
    ~B: destructor
    ~Base: destructor
    }
    /**
     * 结论:
     *  多继承情况下,
     *  - 构造:父类的构造顺序和声明顺序一致(先声明先构造)
     *  - 析构:父类的析构顺序和声明顺序相反(先声明后析构,或者说先构造的后析构)
     */
    
    // output
    测试03:包含成员变量 {
    case 1: 先声明A,再声明B
    MemberA: constructor
    MemberB: constructor
    HasMemberAB: constructor
    ~HasMemberAB: destructor
    ~MemberB: destructor
    ~MemberA: destructor
    case 2: 先声明B,再声明A
    MemberB: constructor
    MemberA: constructor
    HasMemberBA: constructor
    ~HasMemberBA: destructor
    ~MemberA: destructor
    ~MemberB: destructor
    }
    /**
     * 结论:
     *  类中包含成员的情况下
     * - 构造:先构造成员变量,再构造自身
     *        类中成员变量的构造顺序是:先声明先构造,和构造函数中的初始化列表的顺序无关
     * - 析构:先运行类的析构函数,在析构成员,成员的析构顺序是:先声明后析构;
     */
    
    // output
    测试04:既有多继承,又有成员变量 {
    Base: constructor
    A: constructor
    Base: constructor
    B: constructor
    MemberA: constructor
    MemberB: constructor
    DerivedAndMember: constructor
    ~DerivedAndMember: destructor
    ~MemberB: destructor
    ~MemberA: destructor
    ~B: destructor
    ~Base: destructor
    ~A: destructor
    ~Base: destructor
    }
    }
    /**
     * 结论:
     *  既有继承,又有成员变量的情况下
     *  - 构造:先构造父类,再构造成员类,最后构造自身(子类)
     *  - 析构:先析构本身(子类),再析构成员,最后再析构父类
     */
    

    4 结论

    通过以上4个实验,可以得出最终结论:

    1. 构造顺序
      1. a. 先父类,再成员变量(类),最后自身(父类->成员类->自身
      2. 在多继承多成员变量的情况下,都是先声明先构造
    2. 析构顺序
      1. 先自身,再成员变量,最后父类(自身->成员类->父类)
      2. 在多继承多成员的情况下,都是先声明后析构
      3. 或者说,析构顺序和构造顺序相反,先构造的后析构
    3. 作用域
      1. 在不考虑引用计数的情况下,类出了作用域就会被析构
  • 相关阅读:
    Orac and Medians
    牛牛的揠苗助长
    Color Graph
    Spanning Tree Removal【构造】
    A Simple Problem On A Tree
    Spring源码学习笔记(六、Spring启动流程解析:BeanFactory后置处理)
    Spring源码学习笔记(五、Spring启动流程解析:准备BeanFactory)
    一、求最大公约数
    Spring源码学习笔记(四、Spring启动流程解析:概述与准备上下文、获取BeanFactory)
    Spring源码学习笔记(三、路径和占位符,Spring容器如何解析配置信息)
  • 原文地址:https://www.cnblogs.com/HongyunL/p/13976947.html
Copyright © 2020-2023  润新知