定義一個類對象時,首先根據初始化列表初始化類的成員(就算沒有顯式定義初始化列表,編譯器也會默認地初始化一次),然后運行構造函數。因此,類成員的構造函數必定先于類的構造函數運行。
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的析構函數將不會運行,這樣就會造成內存泄露。