• 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
    ", mi);  // 打印语句对实验非常有用;
    12     }
    13     
    14     Test(const Test& obj)
    15     {
    16         mi = obj.mi;
    17         printf("Test(const Test& obj): %d
    ", 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
    ", mi);
    12     }
    13     
    14     Test(const Test& obj)
    15     {
    16         mi = obj.mi;
    17         printf("Test(const Test& obj): %d
    ", 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
    ", 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
    ", mi);
    12     }
    13     
    14     Test(const Test& obj)
    15     {
    16         mi = obj.mi;
    17         printf("Test(const Test& obj): %d
    ", 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
    ", 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,全局对象的构造顺序是不确定的;

  • 相关阅读:
    lsf运行lsload命令显示“lsload: Host does not have a software license”
    linux nfs远程挂载和卸载
    连接EMC存储系统
    linux 挂载ntfs格式的硬盘
    WARN [QuorumPeer[myid=1]/0:0:0:0:0:0:0:0:2181:QuorumCnxManager@584]
    循环更新sqlserver数据库表ID
    【BZOJ 1563】 (四边形优化、决策单调性)
    【BZOJ 3028】 3028: 食物 (生成函数)
    【BZOJ 3027】 3027: [Ceoi2004]Sweet (容斥原理+组合计数)
    【BZOJ 1061】 1061: [Noi2008]志愿者招募 (线性规划与网络流)**
  • 原文地址:https://www.cnblogs.com/dishengAndziyu/p/10906308.html
Copyright © 2020-2023  润新知