[C++]搞清楚类中构造与析构的顺序
定义一个类对象时,首先根据初始化列表初始化类的成员(就算没有显式定义初始化列表,编译器也会默认地初始化一次),然后运行构造函数。因此,类成员的构造函数必定先于类的构造函数运行。
class A { public: A() { puts("In A"); } ~A() { puts("Out A"); } }; class B { public: B() { puts("In B"); } ~B() { puts("Out B"); } }; class D { public: D() { puts("In D"); } ~D() { puts("Out D"); } }; class X { public: X() { puts("In X"); } virtual ~X() { puts("Out X"); } virtual void test() { puts("test in X"); } D d; }; class C:public X { public: C()/*: b(), a()*/ { puts("In C"); } ~C() { puts("Out C"); } virtual void test() { puts("test in C"); } private: A a; B b; }; int main() { X* p = new C; p->test(); delete p; return 0; }
new C的时候,由于C由X继承而来,因此先构造X。首先,按照初始化列表初始化X的成员变量,这里没有初始化列表,系统也会默认地为d进行默认初始化,此时调用D的构造函数,打印In D。(先初始化成员,再运行构造函数)
X的初始化阶段结束后,运行X的构造函数,打印In X。
X部分构造结束后,开始构造C的部分。同样的先进行初始化工作,无论有无初始化列表,无论初始化列表的顺序如何,成员的初始化顺序都按声明顺序进行初始化。打印In A,In B。
C的成员完成初始化后,调用C的构造函数,打印In C。
/*----------------------------------至此,new C的过程完毕-----------------------------------------*/
基类指针调用虚函数,进行动态绑定,输出test in C
/*----------------------------------开始对指针p析构-----------------------------------------*/
析构的顺序就是构造顺序的逆序。就是先析构父类,再析构子类。先析构本类,再析构本类的成员。
于是打印的顺序为O C, O B, O A, O X, O D
这个例子同时也解释了,为什么基类的析构函数要声明为虚函数。如果
//不是虚函数 ~X() { puts("Out X"); }
那么delete p时候会直接析构p的静态类型X,所以C的成员以及C的析构函数将不会运行,这样就会造成内存泄露。