C++中對象的構造順序


1,C++ 中的類可以定義多個對象,那么對象構造順序是怎樣的?

    1,很多的 bug 是由對象的構造順序造成的,雖然它不難;

    2,對象的構造往往和構造函數牽涉在一起,構造函數的函數體又可能由非常復雜的程序邏輯組成的;

    3,這樣就有可能引入了一個問題,不同類的它們的構造函數中的程序邏輯也許是相互依賴的,當這種相互依賴一旦發生,那么對象的構造順序就很可能導致程序中非常難以調試的 bug 出現;

    4,在工程中,由於對象的構造順序而造成的軟件 bug 非常之多,因此有必要梳理下對象的構造順序;

 

2,對於局部對象:

    1,當程序執行流到達對象的定義語句時進行構造;

       1,構造時調用構造函數;

   

3,局部對象的構造順序實例分析:

     1,代碼示例:

 1 #include <stdio.h>
 2 
 3 class Test
 4 {
 5 private:
 6     int mi;
 7 public:
 8     Test(int i)
 9     {
10         mi = i;
11         printf("Test(int i): %d\n", mi);  // 打印語句對實驗非常有用;
12     }
13     
14     Test(const Test& obj)
15     {
16         mi = obj.mi;
17         printf("Test(const Test& obj): %d\n", mi);
18     }
19 };
20 
21 int main()
22 {
23     int i = 0;
24     Test a1 = i;  // 第一個被構造的對象
25         
26     while( i < 3 )
27     {
28         Test a2 = ++i;  // 第二個被構造的對象;反復的被構造三次對象;
29     }
30     
31 // goto End;  // 下面的輸出不會執行了;程序執行流跳過了程序調用的定義,則對象就沒有被構造了; 
32     if( i < 4 )  
33     {
34         Test a = a1;  // 第三個被構造的對象;Test(const Test& obj): 3
35     }
36     else
37     {
38         Test a(100);
39     }
40 // End:
41     return 0;
42 }

  2,這個實驗說明:

         1,程序執行流直接和局部對象的構造順序息息相關,如果非法的改變程序執行流,那么程序可能產生災難性的錯誤,參見如下示例:

 1 #include <stdio.h>
 2 
 3 class Test
 4 {
 5 private:
 6     int mi;
 7 public:
 8     Test(int i)
 9     {
10         mi = i;
11         printf("Test(int i): %d\n", mi);
12     }
13     
14     Test(const Test& obj)
15     {
16         mi = obj.mi;
17         printf("Test(const Test& obj): %d\n", mi);
18     }
19     
20     int getMi()
21     {
22         return mi;
23     }
24 };
25 
26 int main()
27 {
28     int i = 0;
29     Test a1 = i; // Test(int i): 0
30         
31     while( i < 3 )
32     {
33         Test a2 = ++i; // Test(int i): 1, 2, 3
34     }
35 goto End;       
36         Test a(100);  // 這里程序執行流跳過了對象的定義,因此 a 這個對象是沒有被構造的,沒有被構造意味着 a 這個對象它的狀態是沒有被初始化的,如果后面使用這個對象,則會產生災難性的錯誤;g++ 可以幫助我們報出這個錯誤,但是其它編譯器有可能不能,因為這不是標准,比如 Visual Studio 中的編譯器;
37 End:
38     printf("a.mi = %d\n", a.getMi());  // 這里打印隨機值;a 對象沒有被初始化,因為構造函數沒有被調用; 
39     
40     return 0;
41 }

    2,這個實驗說明:

           1,對象的構造如果不是顯示的調用,則只發生在對象定義(運行時)之時;

           2,編譯時只產生沒有初始化的對象(這里很模糊,不清楚);

           3,程序執行流和程序的編譯不相關,編譯還是會都編譯的。執行確定了構造函數的調用,而編譯則是生成空間;

   

4,對於堆對象:

    1,當程序執行流到達 new 語句時創建對象;

       1,創建對象就要觸發構造函數的調用;

       2,其實堆對象構造順序也和程序執行流相關,只不過說由於引入了 new 關鍵字,堆對象的構造順序比局部對象更容易確認些;

    2,使用 new 創建對象將自動觸發構造函數的調用;

   

5,堆對象的構造順序編程實驗:

 1 #include <stdio.h>
 2 
 3 class Test
 4 {
 5 private:
 6     int mi;
 7 public:
 8     Test(int i)
 9     {
10         mi = i;
11         printf("Test(int i): %d\n", mi);
12     }
13     
14     Test(const Test& obj)
15     {
16         mi = obj.mi;
17         printf("Test(const Test& obj): %d\n", mi);
18     }
19     
20     int getMi()
21     {
22         return mi;
23     }
24 };
25 
26 int main()
27 {
28     int i = 0;
29     Test* a1 = new Test(i); // Test(int i): 0
30         
31     while( ++i < 10 )
32         if( i % 2 )
33             new Test(i); // Test(int i): 1, 3, 5, 7, 9
34         
35     if( i < 4 )
36         new Test(*a1);
37     else
38         new Test(100); // Test(int i): 100
39         
40     return 0;
41 }

    1,堆對象的創建也會受到 goto 語句的影響,因為 goto 語句會改變程序執行流,也就可能繞過堆對象的創建;

   

6,對於全局對象:

    1,全局對象的構造順序是不確定的;

       1,這里帶來的 bug 是非常多的;

       2,C++ 標准沒有定義全局對象的構造順序;

    2,不同的編譯器使用不同的規則確定構造順序;

   

7,全局對象的構造順序:

    1,test.h文件:

 1 #ifndef _TEST_H_
 2 #define _TEST_H_
 3 
 4 #include <stdio.h>
 5 
 6 class Test
 7 {
 8 public:
 9     Test(const char* s)
10     {
11         printf("%s\n", s);
12     }
13 };
14 
15 #endif

    2,t1.cpp 文件:

1 #include "test.h"

2

3 Test t1("t1"); 

    3,t2.cpp 文件:

1 #include "test.h"

2

3 Test t2("t2"); 

    4,t3.cpp 文件:

1 #include "test.h"

2

3 Test t3("t3"); 

    5,主函數:

1 #include "test.h"
2 
3 Test t4("t4");  // 全局對象的構造一定會在 main 函數之前完成,和程序執行流沒有關系;main() 函數執行之前,沒有程序執行流的概念,如果出現多個全局對象的時候,它們的構造順序就不確定了;尤其是兩個不同的操作系統更是不同;
4 int main()
5 {
6     Test t5("t5");
7 }

    1,在以后的開發過程中,要避免全局對象之間的相互依賴;

    2,當今的軟件開發領域,盡量的不使用全局變量的,其中原因之一就是全局的變量它們的初始化順序是不確定的,面向對象領域就變成了全局對象的構造順序是不確定的;

    3,goto 語句也是禁用的,因為它會改變程序執行流,這樣會造成局部對象構造順序產生錯亂,有可能導致程序里面局部對象構造出現問題,進而產生不可預計的災難性錯誤;

   

8,小結:

    1,局部對象的構造順序依賴於程序的執行流;

    2,堆對象的構造順序依賴於 new 的使用順序;

    3,全局對象的構造順序是不確定的;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM