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 << "}\n\n" << 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 << "}\n\n" << 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 << "}\n\n" << std::endl;
}

void Test04_DerivedAndMember() {
  std::cout << "测试04:既有多继承,又有成员变量 {" << std::endl;
  {
    DerivedAndMember dm;
  }
  std::cout << "}\n\n" << 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. 在不考虑引用计数的情况下,类出了作用域就会被析构
posted @ 2020-11-15 15:49  HongyunL  阅读(924)  评论(0编辑  收藏  举报