1,C++ 中的类可以定义多个对象,那么对象构造顺序是怎样的?

1,很多的 bug 是由对象的构造顺序造成的,虽然它不难;

2,对象的构造往往和构造函数牵涉在一起,构造函数的函数体又可能由非常复杂的程序逻辑组成的;

3,这样就有可能引入了一个问题,不同类的它们的构造函数中的程序逻辑也许是相互依赖的,当这种相互依赖一旦发生,那么对象的构造顺序就很可能导致程序中非常难以调试的 bug 出现;

4,在工程中,由于对象的构造顺序而造成的软件 bug 非常之多,因此有必要梳理下对象的构造顺序;

2,对于局部对象:

1,当程序执行流到达对象的定义语句时进行构造;

1,构造时调用构造函数;

3,局部对象的构造顺序实例分析:

 1,代码示例:

 #include <stdio.h>

 class Test
{
private:
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(int i): %d\n", mi); // 打印语句对实验非常有用;
} Test(const Test& obj)
{
mi = obj.mi;
printf("Test(const Test& obj): %d\n", mi);
}
}; int main()
{
int i = ;
Test a1 = i; // 第一个被构造的对象 while( i < )
{
Test a2 = ++i; // 第二个被构造的对象;反复的被构造三次对象;
} // goto End; // 下面的输出不会执行了;程序执行流跳过了程序调用的定义,则对象就没有被构造了;
if( i < )
{
Test a = a1; // 第三个被构造的对象;Test(const Test& obj): 3
}
else
{
Test a();
}
// End:
return ;
}

  2,这个实验说明:

  1,程序执行流直接和局部对象的构造顺序息息相关,如果非法的改变程序执行流,那么程序可能产生灾难性的错误,参见如下示例:

 #include <stdio.h>

 class Test
{
private:
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(int i): %d\n", mi);
} Test(const Test& obj)
{
mi = obj.mi;
printf("Test(const Test& obj): %d\n", mi);
} int getMi()
{
return mi;
}
}; int main()
{
int i = ;
Test a1 = i; // Test(int i): 0 while( i < )
{
Test a2 = ++i; // Test(int i): 1, 2, 3
}
goto End;
Test a(); // 这里程序执行流跳过了对象的定义,因此 a 这个对象是没有被构造的,没有被构造意味着 a 这个对象它的状态是没有被初始化的,如果后面使用这个对象,则会产生灾难性的错误;g++ 可以帮助我们报出这个错误,但是其它编译器有可能不能,因为这不是标准,比如 Visual Studio 中的编译器;
End:
printf("a.mi = %d\n", a.getMi()); // 这里打印随机值;a 对象没有被初始化,因为构造函数没有被调用; return ;
}

    2,这个实验说明:

    1,对象的构造如果不是显示的调用,则只发生在对象定义(运行时)之时;

    2,编译时只产生没有初始化的对象(这里很模糊,不清楚);

    3,程序执行流和程序的编译不相关,编译还是会都编译的。执行确定了构造函数的调用,而编译则是生成空间;

4,对于堆对象:

1,当程序执行流到达 new 语句时创建对象;

1,创建对象就要触发构造函数的调用;

2,其实堆对象构造顺序也和程序执行流相关,只不过说由于引入了 new 关键字,堆对象的构造顺序比局部对象更容易确认些;

2,使用 new 创建对象将自动触发构造函数的调用;

5,堆对象的构造顺序编程实验:

 #include <stdio.h>

 class Test
{
private:
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(int i): %d\n", mi);
} Test(const Test& obj)
{
mi = obj.mi;
printf("Test(const Test& obj): %d\n", mi);
} int getMi()
{
return mi;
}
}; int main()
{
int i = ;
Test* a1 = new Test(i); // Test(int i): 0 while( ++i < )
if( i % )
new Test(i); // Test(int i): 1, 3, 5, 7, 9 if( i < )
new Test(*a1);
else
new Test(); // Test(int i): 100 return ;
}

1,堆对象的创建也会受到 goto 语句的影响,因为 goto 语句会改变程序执行流,也就可能绕过堆对象的创建;

6,对于全局对象:

1,全局对象的构造顺序是不确定的;

1,这里带来的 bug 是非常多的;

2,C++ 标准没有定义全局对象的构造顺序;

2,不同的编译器使用不同的规则确定构造顺序;

7,全局对象的构造顺序:

1,test.h文件:

 #ifndef _TEST_H_
#define _TEST_H_ #include <stdio.h> class Test
{
public:
Test(const char* s)
{
printf("%s\n", s);
}
}; #endif

2,t1.cpp 文件:

#include "test.h"

Test t1("t1");

3,t2.cpp 文件:

#include "test.h"

Test t2("t2");

4,t3.cpp 文件:

#include "test.h"

Test t3("t3");

5,主函数:

 #include "test.h"

 Test t4("t4");  // 全局对象的构造一定会在 main 函数之前完成,和程序执行流没有关系;main() 函数执行之前,没有程序执行流的概念,如果出现多个全局对象的时候,它们的构造顺序就不确定了;尤其是两个不同的操作系统更是不同;
int main()
{
Test t5("t5");
}

1,在以后的开发过程中,要避免全局对象之间的相互依赖;

2,当今的软件开发领域,尽量的不使用全局变量的,其中原因之一就是全局的变量它们的初始化顺序是不确定的,面向对象领域就变成了全局对象的构造顺序是不确定的;

3,goto 语句也是禁用的,因为它会改变程序执行流,这样会造成局部对象构造顺序产生错乱,有可能导致程序里面局部对象构造出现问题,进而产生不可预计的灾难性错误;

8,小结:

1,局部对象的构造顺序依赖于程序的执行流;

2,堆对象的构造顺序依赖于 new 的使用顺序;

3,全局对象的构造顺序是不确定的;

C++中对象的构造顺序的更多相关文章

  1. C++类继承中,基类/当前对象属性/当前对象的构造顺序

    [1]中提到,规范的派生类构造函数三个要点: 首先创建基类对象 应通过成员初始化列表,创建基类对象 应该初始化本派生类新增的成员变量 那在构造派生类实例的过程中,其基类(以及多继承的时候多个基类)/当 ...

  2. js中对象的输出顺序

    前言:最近用for-in时,看到说for-in不能保证遍历的对象顺序,对此有些疑问,于是便研究了下,本文做简要说明. 现象 let obj = { a: 'a', b: 'b', 1: 1, 2: 2 ...

  3. c++中对象的构造和销毁

    对象的初始化 如下 ckasss Person { public: ]; char sex; int age; }; Person p={}; //对象初始化 构造数组对象时,需要一个没有参数的构造函 ...

  4. 10.C++-构造函数初始化列表、类const成员、对象构造顺序、析构函数

    首先回忆下,以前学的const 单独使用const修饰变量时,是定义的常量,比如:const int i=1; 使用volatile const修饰变量时,定义的是只读变量 使用const & ...

  5. C++解析(12):初始化列表与对象构造顺序、析构顺序

    0.目录 1.类成员的初始化 2.类中的const成员 3.对象的构造顺序 3.1 局部对象的构造顺序 3.2 堆对象的构造顺序 3.3 全局对象的构造顺序 4.对象的析构顺序 5.小结 1.类成员的 ...

  6. C++ 对象构造顺序、构析函数、临时对象。

    对象的构造顺序: 1.对于局部对象,构造顺序是根据程序执行流进行构造,从上到下. #include <stdio.h> class Test { int mi; public: Test( ...

  7. [百度空间] [转] 在 Visual C++ 中控制全局对象的初始化顺序

    from: http://blog.csdn.net/classfactory/archive/2004/08/07/68202.aspx 在 C++ 中,同一个翻译单位(.cpp文件)里的全局对象的 ...

  8. C++深度解析教程学习笔记(6)对象的构造和销毁

    1. 对象的初始化 (1)从程序设计的角度看,对象只是变量,因此: ①在栈上创建对象时,成员变量初始化为随机值 ②在堆上创建对象时,成员变量初始化为随机值 ③在静态存储区创建对象时,成员变量初始化为 ...

  9. Java中对象构造

    构造函数 作用:在构造对象的同时初始化对象.java强制要求对象 诞生同时被初始化,保证数据安全. 调用过程和机制:①申请内存,②执行构造函数的函数体,③返回对象的引用. 特点:与类同名,无返回类型, ...

随机推荐

  1. 【NOIP2016提高A组模拟8.15】Password

    题目 分析 首先我们知道,原A序列其实表示一个矩阵,而这个矩阵的对角线上的数字就是答案B序列. 接着\(a.b>=gcd(a,b)\),所以序列A中的最大的数就是ans[1],第二大的数就是an ...

  2. 浅谈Android的资源编译过程

    转载自 http://www.cnblogs.com/dyllove98/p/3144950.html 好长,记录下,一次看完感觉像没看一样 Android APK 一.APK的结构以及生成 APK是 ...

  3. C# 获得对象的命名空间 ?.

    A a = new A(); var t = a?.ToString(); //t = WebApplication1.Controllers.A //获得命名空间和类名 var t1 = (A)nu ...

  4. BZOJ 4399: 魔法少女LJJ 线段树合并 + 对数

    Description 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着 ...

  5. windows10 gcc编译C程序(分步编译)

    下面演示gcc对C源程序的分步编译过程: 1. 编译(Compile) gcc hello.cpp -c # 生成hello.o,目标文件名字和源文件名字一样,VC编译会生成.ojb文件,gcc编译器 ...

  6. (55)Linux驱动开发之一驱动概述

                                                                                                      驱动 ...

  7. Linux root用户密码重置,远程登陆,文件基本属性

    Linux root用户密码重置,远程登陆,文件基本属性 忘记Linux系统的root密码,linux系统忘记root密码的情况该怎么办呢?重新安装系统吗?当然不用!进入单用户模式更改一下root密码 ...

  8. Redis缓存雪崩和缓存穿透等问题

    穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透. 解决办法:①用一个bitma ...

  9. C# walls

    在学习C#的阶段中,我们一点一点的往前爬, 此代码需要添加selenium ,和 获取 引用. using Ivony.Html.Parser; using Ivony.Html; using Ope ...

  10. SQL Server Availability Group Failover 测试

    兼容性测试: 测试脚本: 环境:windows failover cluster 主库执行脚本: USE [master]GOALTER AVAILABILITY GROUP [test_AG]MOD ...