作者主页:@烽起黎明
学习社区:烈火神盾
专栏链接:C++
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的
本文,我们将在前面的基础上继续展开学习,去探究一下拷贝构造在拷贝对象时会发生一些编译器优化
explicit
关键字的时候,我有提到下面这种写法会引发【隐式类型转换】,而且还画了对应的图示,中间会通过1构造产生一个A类型的临时对象,然后用再去调用拷贝构造完成拷贝。A aa1 = 1;
首先来看到的场景是【传值传参】,对象均是使用上面
aa1
//传值传参
void func1(A aa)
{
}
请你思考一下这种形式编译器还会像上面那样去优化拷贝构造吗
func1(aa1);
aa
,main函数中的aa1
也会析构,不做展示】func1(3);
aa1
对象】func1(A(3));
A(3)
就是一个很明显的有参构造,实例化出一个对象后那就是拷贝构造,但是这里因为编译器的优化,所以直接变成了一个构造接下去来看到的场景是【传引用传参】,传入的值还是上面的这三种,只是会通过传引用来接收
//传引用传参
void func2(const A& aa) //不加const会造成权限放大
{
}
那通过引用接收aa1
会发生什么呢?
func2(aa1);
aa
就是aa1
的别名,无需构造产生一个新的对象,也不用去拷贝产生一个,直接用形参部分这个就可以了,现在知道引用传参的好处了吧func2(3);
aa
就是这个临时对象的别名,所以无需调用拷贝构造,所以也是当回到主函数中才调用析构函数,此时析构的就是这个临时对象const
做修饰,否则就会造成权限放大func2(A(3));
看完【传值传参】和【传引用传参】,我们来总结一下
接下去我们来讲讲函数返回时候编译器优化的场景,首先是【传值返回】
//传值返回
A func3()
{
A aa;
return aa;
}
若是直接去调用上面这个func3(),会发生什么呢?
func3();
此处在函数调用的地方我使用一个对象去做了接收,那在上面【构造 + 拷贝构造】的基础上就会再多出一个【拷贝构造】,即为【构造 + 拷贝构造 + 拷贝构造】
A aa2 = func3();
这里可能比较抽象,我画个图来解说一下
aa
与A
不是同一个表示式,所以不会引发编译器的优化;对于第二个来说,因为又拿了一个A的对象作为接收,所以又会产生一个拷贝构造。在这里编译器就要出手了,它会觉得两个拷贝构造过于麻烦,所以会直接优化成一个=
便是拷贝构造;若是一个对象已经实例化出来了,使用=
便是赋值重载A aa2;
aa2 = func3();
然后来说说【传引用返回】,不过若是你知道引用返回的一些机制的话,就可以清楚我下面这样其实是错误的,因为
aa
属于局部变量,出了当前作用域会销毁,所以不可以使用传引用返回,具体以下细述
A& func4()
{
A aa;
return aa;
}
首先来看下直接调用的结果会是怎样的
func4();
那我若是用一个返回值去接收的话,此时就可以看出引用返回临时对象的问题了
A aa3 = func4();
_a
就是一个随机值A func4()
还记得上面讲到的【匿名对象】吗,也可以使用它返回哦,效率还不低呢!
//匿名对象返回
A func5()
{
return A(); //返回一个A的匿名对象
}
先调用一下看看会怎么样
func5();
A()
你可以就把它看做是一个表达式,一个【构造】+【拷贝构造】就被优化成了直接构造A aa4 = func5();
aa4
,第二次是aa3
,第三次便是一开始就有的aa1
了,通过这么调试观察,希望你能真正看懂编译器的思维
而且可以观察到匿名对象返回也不会造成随机值现象,因为本质使用的还是【传值返回】,这里不可以使用【传引用返回】,因为匿名对象构建出来的也是一个临时对象,具有常性,会造成权限放大
看完了上面这一些系列拷贝对象时编译器的优化,我们来做一个总结
对象返回总结
函数传参总结
const
+ &
传参,减少拷贝的同时防止权限放大以上就是本文要介绍的所有内容,感谢您的阅读