
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
友元在前面文章IO流的输出输出重载的地方用过一次
这里有两个类A,B,此时有一个函数既要访问A,也要访问B,就要使用友元了,一个函数是能够成为多个类的友元的

如图函数内就可以访问A,B的私有或者保护了,但是这里代码编译不通过。
不通过是前置声明的问题,在用任何的变量,函数,类型的时候都要在当前位置的前面声明或定义了后面才能用,因为编译器是向上编译检查的(节省编译时间,同时向上向下查找出处浪费时间),图中A类中的形参B向上找的时候找不到,也就是A,B类之间存在相互依赖。 解决方法就是加一个类的前置声明

图中D这个类要大量的访问C这个类的私有。虽然可以把D中的成员函数声明为C的友元,但是成员函数多的时候很麻烦,就有了一种更简单粗暴的方法,友元类

友元不是相互的,友元是一种单向关系,D成为了C的友元,在D中可以访问C,但在C中是无法访问D的,除非在D中加C的友元声明,是可以互相成为友元的

但是这样还是编译不通过的,虽然前置声明了D的类型,但只能解决用D这个类型,但是D里面的细节(如成员变量)还是不能用的(D中有无_b这个成员编译器是不知道的),而且这里也不需要前置声明,已经友元声明D是一个类了,这时候就要做声明和定义分离

上图用声明和定义分离就能很好的解决这个问题了,用任何东西之前,编译器的原则都是在前面找到这个东西,比如:类,函数,变量


这里只会计算A类型对象的大小(静态成员不存在对象,静态成员存在静态区),A的内部是没有B成员的,所以内部类不是成员

在类的内部可以用静态成员,内部类也是在A类的内部


该代码就可以改为内部类,原代码Sum类计算出的结果可以被其定义的对象更改的,或者其他的类定义的对象都可以更改,所以Sum类放到全局不好

所以可以把Sum设计为Solution的一个私有的内部类,这样其他类的对象就无法改变结果了。还可以把Sum里的静态成员变量放到Solution之中,这样Solution之中可以用这两个成员,Sum也可以用(内部类是外部类的友元),很便捷

有名对象和匿名对象都是调用一个类的构造函数定义出来的对象

图中调用Sum_Soluton这个函数,要用有名对象,而使用匿名对象写成一行更加简洁,对于只使用一次的场景更加方便,第二次结果变大的原因是,第二次的结果是在第一次的结果下累加的

解决办法就是多次调用的时候配一个Clear的函数,每次调用完后把_i和_ret初始化一下
再补充一个点,引用是可以引用匿名对象的,但匿名对象和临时对象类似,都具有常性(const-ness,对象或数据 “不可被修改” 的特性),常性具有以下作用



这里还可以用匿名对象给类类型缺省值,缺省值可以给常量,也可以给全局对象

类类型给缺省参数很多时候就会给匿名对象,这里的const引用就延长了匿名对象的生命周期,此时的匿名对象生命周期就和s一样了,s对象的生命周期就是当前main函数的作用域
以上就是匿名对象最常见的两个场景
隐式类型转换就是一个经典连续拷贝的优化

接下来看一下传参的优化



匿名对象不优化的情况下:



这也是跨行优化,本质上可以理解为aa是aa1的引用,这里不存在先有aa后有aa1的问题,二者的栈帧的空间是一起开好的,aa1的空间在没有调用f2的时候就已经有了

可以看到二者的地址也是一样的,这也是说现在编译器优化非常“激进的”原因,非常极致,直接把中间那么多步骤一把全摘了,gcc9.4的优化也是如此
这里再细说一下优化的核心逻辑,这一块代码严格来说有两个栈帧,分别是main函数和f2。默认情况是不能把aa作为返回值的,调用f2结束之后其栈帧销毁,aa也就跟着销毁了,所以通常是把aa放在一个临时对象

aa的临时对象是有两种存在方式,第一种就是临时对象比较小,一般存在寄存器当中,大一点会放到两个栈帧中间(main提前调f2的时候,压的参数,返回值就会放到中间的栈帧),此时f2的栈帧销毁了,中间这个栈帧还在,等着main函数内接收好了之后,这个临时对象再销毁,再回收栈帧。
编译器优化的时候,会先通过语法分析发现aa最终构造在aa1上,就会直接把中间的临时对象省略,一把拿下
这个优化的名字就叫NRVO,其是在C++17中的标准提出的。N就是name,返回一个有名字的对象,R就是return


URVO就是返回一个匿名对象,C++17是规定了URVO的优化,NRVO还没有规定,但是代码中的NRVO是有优化的,所以编译器是超前的
现在来看一下匿名对象的优化

可以看到也是直接构造了,完全不优化的分析就不写了,和前面有名对象的完全不优化一样
最后补充一点,如果像下面这种写法就会打乱优化(Debug)


构造aa1 构造aa 用aa拷贝构造临时对象临时对象 aa销毁 临时对象赋值 临时对象销毁 aa1销毁
Release还是会在临时对象这里做些优化

还是更推荐第一种写法,效率更高