博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++中的临时对象
阅读量:5158 次
发布时间:2019-06-13

本文共 4491 字,大约阅读时间需要 14 分钟。

1,临时对象神秘在于不知不觉就请入程序当中,并且给程序带来了一定的问题;

 

2,下面的程序输出什么?为什么?

1 #include 
2 3 class Test 4 { 5 int mi; 6 public: 7 Test(int i) 8 { 9 mi = i;10 }11 12 Test() // 这里程序作者想要代码复用,直接调用已经构造好的函数来完成没有参数的构造函数的函数体;13 {14 Test(0); // 得到临时对象,没有名字,就意味着作用域只在这个语句,过了这个语句,就没法被访问到了;这里的语句在这里没有什么作用,等价于空的语句;15 }16 17 void print()18 {19 printf("mi = %d\n", mi);20 }21 };22 23 int main()24 {25 Test t;26 27 t.print(); // 期望 mi 为 0;但是结果却是随机值;28 29 return 0;30 }

 

3,程序意图:

        1,在 Test() 中以 0 作为参数调用 Test(int i);

        2,将成员变量 mi 的初始值设置为 0;

   运行结果:

       1,成员变量 mi 的值为随机值;

  

4,构造函数是一个特殊的函数:  

    1,是否可以直接调用?

       1,给编译器主动调用的,但也可直接手工调用;

    2,是否可以在构造函数中调用构造函数?

       1,从编译器的编译结果来看在语法上合法;

    3,直接调用构造函数的行为是什么?

       1,直接调用构造函数将会产生一个临时对象;

           1,是一个合法的 C++ 对象,生命期只有一条语句时间;

           2,过了这个语句临时对象就会被自动析构而不复存在;

           3,临时对象是没有名字的;

       2,临时对象的生命周期只有一条语句;

       3,临时对象的作用域只在一条语句中;

       4,临时对象是 C++ 中值得警惕的灰色地带;

           1,同 C 中的野指针一样必须警惕;

          

5,避免因代码复用调用构造函数而带来的临时对象的解决方案:

1 #include 
2 3 class Test { 4 int mi; 5 6 void init(int i) 7 { 8 mi = i; 9 }10 11 public:12 Test(int i) 13 {14 init(i); 15 }16 17 Test() 18 {19 init(0); // 工程中代码复用方式20 }21 22 void print() {23 printf("mi = %d\n", mi);24 }25 };26 27 28 int main()29 {30 Test t;31 32 t.print();33 34 return 0;35 }

 

6,临时对象的测试:

1 #include 
2 3 class Test { 4 int mi; 5 6 void init(int i) 7 { 8 mi = i; 9 }10 11 public:12 Test(int i) 13 {14 printf("Test(int i)\n");15 init(i); 16 }17 18 Test() 19 {20 printf("Test()\n");21 init(0); 22 }23 24 void print() {25 printf("mi = %d\n", mi);26 }27 28 ~Test()29 {30 printf("~Test()\n");31 }32 };33 34 35 int main()36 {37 printf("main begin1\n");38 39 Test(); // 产生临时对象;打印 ==> Test() ~Test()40 41 Test(10); // 产生临时对象;打印 ==> Test(int i) ~Test()42 43 printf("main end1\n");44 45 printf("main begin2\n");46 47 Test().print(); // 临时对象生成之后直接调用 print() 函数;这里可以通过编译,因为调用了构造函数之后呢就会产生临时对象了,通过合法的 C++ 对象加上点操作符来调用对应的成员函数是完全没有问题,完全合法的;产生临时对象;打印 ==> Test() mi = 0 ~Test()48 Test(10).print(); //产生临时对象;打印 ==> Test(int i) mi = 10 ~Test() 49 50 printf("main end2\n");51 52 return 0;53 }

    1,这里仅是教育需要,向大家介绍这个知识点,在以后工程开发中,万不可这样写代码,一定要去避免临时对象的产生和使用;

      

7,现代 C++ 编译器在不影响最终执行结果的前提下,会尽力减少临时对象的产生;

 

8,神秘的临时对象编程实验:

    1,证明 C++ 编译器在极力的减少临时对象的产生;

1 #include 
2 3 class Test 4 { 5 int mi; 6 7 public: 8 Test(int i) 9 {10 printf("Test(int i) : %d\n", i);11 mi = i;12 }13 14 Test(const Test& t)15 {16 printf("Test(const Test& t) : %d\n", t.mi);17 mi = t.mi;18 }19 20 Test()21 {22 printf("Test()\n");23 mi = 0;24 }25 26 int print()27 {28 printf("mi = %d\n", mi);29 }30 31 ~Test()32 {33 printf("~Test()\n");34 }35 };36 37 Test func()38 {39 return Test(20); // 生成一个临时对象,函数调用结束后就立即销毁临时对象了40 }41 42 int main()43 {44 /*45 Test t(10); // 等价于 Test t = Test(10); 于是可解读为:1,生成临时对象 2,用临时对象初始化即将生成的 t 对象,于是必然涉及到调用拷贝构造函数,但是编译器打印的结果为 Test(int i) : 10 mi = 10 ~Test()根本没有任何拷贝构造函数的迹象产生;编译器根本没有像我们解读的上述的两个步骤执行;这是因为现代的 C++ 编译器都会尽量的减少临时对象的产生;从执行结果来看,上面的分析是没有任何错误的,只是上面的还要再调用一次拷贝构造函数,通过上面的分析,即便结果没有任何的变化,但是效率降低了,因此在这个地方 C++ 编译器为了杜绝临时对象的产生,直接将Test t = Test(10) 等价成为了 Test t = 10,这样就杜绝了临时对象的产生;杜绝临时对象的产生是因为其往往会带来性能上面的题,先生成一个临时对象调用了一次构造函数,再将临时对象通过拷贝构造函数来初始化 t,也就是说调用了两次构造函数,如果说我们极力的减少临时对象的产生,那么通过上述第二个方式等价 ,这样调用一次构造函数就可以了,少调用了一次拷贝构造函数,性能就提升了;在这个地方从性能上我们没有多大体会,但在实际的工程 开发中,构造函数里面所做的初始化工作往往是纷繁复杂的,比方说有些类的对象在初始化的时候,在调用构造函数的时候很可能打开外部设备,对设备进行初始设置等等,如果这个时候多了一次构造函数的调用,时间就消耗了;为什么要这样呢?因为 C++ 天生继承了 C 的特性,C 的一个最大特性就是高效,所以 C++ 的一个特性也是要极力的做到高效,该省掉临时对象的地方,就该省掉; 46 */ 47 Test t = Test(10); // ==> Test t = 10;48 49 t.print();50 51 Test tt = func(); // ==> Test tt = Test(20); ==> Test tt = 20; fun() 返回一个临时对象,到达了赋值符号右侧,此时临时对象里面的值会初始化 t 对象;当代的 C++ 编译器,在不影响最终结果的情况下,会极力的减少临时对象的产生;52 53 tt.print(); // 打印 mi = 20;54 55 return 0;56 }

 

9,小结:

    1,直接调用构造函数将产生一个临时对象;

    2,临时对象是性能的瓶颈,也是 bug 的来源之一;;

       1,C++ 中除了野指针,还有临时对象;

       2,工作中,如果出现了奇怪的问题,要从这两个方面考虑;

    3,现代 C++ 编译器会尽力避开临时对象;

       1,避开的前提是不影响最终的执行结果;

    4,实际工程开发中需要人为的避开临时对象;

       1,避免调用构造函数来初始化;

       2,设计函数不要引入像 fun() 函数中的临时对象;

转载于:https://www.cnblogs.com/dishengAndziyu/p/10906712.html

你可能感兴趣的文章
apply,call,bind的区别
查看>>
也谈谈学习
查看>>
针对于多线程概念的理解
查看>>
宿主机为linux、windows分别实现VMware三种方式上网(转)
查看>>
红黑树
查看>>
关于异步的初步认识
查看>>
vue--mixins
查看>>
iOS+HTML5
查看>>
洛谷P1004 方格取数
查看>>
PHP服务器端跨域
查看>>
大白话解析模拟退火算法(转载)
查看>>
虚拟机中3种常见的网络模式
查看>>
三层交换机的设置
查看>>
汇编语言:第九章 转移指令的原理
查看>>
内核的ramdisk
查看>>
Gerrit+apache+H2数据库简单安装配置及建库流程
查看>>
(第三周)团队模式中对交响乐团模式的理解
查看>>
Python2和Python3共存安装robotframework
查看>>
从源代码分析DbSet如何通过ObjectStateManager管理entity lifecycle的生命周期
查看>>
ABAP OO的八大理由(十四)
查看>>