一、优化实例
我们在调用函数来进行传参和返回返回值的时候,往往会面临多次的拷贝,创建多个临时变量,这毫无疑问会加重编译器的负担,所以编译器也会进行自身的优化。
接下来我们通过实例来好好理解一下优化的都有哪些地方。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
以上这些都是铺垫用的,构建类,运算符重载以及功能函数。下面是用来调试编译器优化的代码。
int main()
{
// 一、传值传参
A aa1;
f1(aa1);
cout << endl;
// 二、传值返回
f2();
cout << endl;
// 三、隐式类型,连续构造+拷贝构造
f1(1);
cout << endl;
// 四、一个表达式中,连续构造+拷贝构造
f1(A(2));
cout << endl;
// 五、一个表达式中,连续拷贝构造+拷贝构造
A aa2 = f2();
cout << endl;
// 六、一个表达式中,连续拷贝构造+赋值重载
aa1 = f2();
cout << endl;
return 0;
}
我们来对这段代码进行分析。
-
先有了一个普通的构造,创建了aa1,然后调用了f1函数,f1函数的参数是一个传值类型,所以在这里还需要进行一次拷贝构造。
所以传值传参就是一次构造加一次拷贝构造,并没有优化。 -
在进入f2函数之后,首先是创建了一个A类的对象,这是普通的构造,随后这个函数进行了一次传值返回,而传值返回需要进行一次拷贝构造。
所以传值返回也是一次构造加一次拷贝构造,也不存在优化。 -
首先我们要可以看出来这是一个隐式类型转换,是将int类型转换成自定义类型,在这个过程经历的是一次构造加一次拷贝构造,这次拷贝构造是直接将值赋给了f1的参数aa,然后这里就涉及到了编译器的自身优化。当在同一个表达式中,有连续的构造加拷贝构造时,编译器会自动将这几个步骤合并成一个直接构造。 特别需要注意的是,要在同一个表达式中,因为这个式子和第一个例子传值传参的区别就在于,第一个例子用了两个表达式来完成这个操作。我们同时还要注意,这种隐式类型转换只限于单参数的情况下进行。
-
这种定义是使用了匿名对象的方法,先构造了一个值为2的类,本质上和上面这种没有区别,都**是构造然后加拷贝构造,**又因为在同一个表达式中,所以编译器也会将其进行优化,将其变为一个直接构造。
-
在f2函数中,先是进行了一个构造,然后因为是传值返回所以这中间还有一个拷贝构造,在函数执行完毕之后,我们发现,等式的左边又定义了一个新的对象,所以这又是一次拷贝构造。在这个表达式中,我们可以发现,一共经历了一次构造加两次拷贝构造。于是,编译器又会进行自身的优化,就是将最后两次拷贝构造优化为一次拷贝构造。
这里的构造加拷贝构造就不会再进行第二次优化了,因为f2函数中的aa的构造和后面两次拷贝构造并不在同一个表达式中,所以不能进行优化。 -
最后一个比较特殊,即有运算符重载的问题,又有传引用的问题。我们一点一点来看,首先是传引用,我们在传引用的时候,可以大致理解为c语言的指针,所以这里面并不存在类的拷贝构造,传过去的是类本身的地址,包括返回值也一样,都不涉及拷贝的事情。第六个跟第五个的区别就在于,上一个是在定义的时候使用的运算符重载,所以是属于拷贝构造,但是这个是先进行了构造,然后再进行的运算符重载,就是一个单纯的赋值重载而已。所以整体下来是一个构造然后加一个拷贝构造,最后来一个赋值重载。 但是拷贝构造加赋值重载的形式并不会让编译器对其进行优化。
二、总结
1.优化总结
- 首先应该注意所有连续的操作是否都在同一个表达式中,这是一切优化的前提,如果不在同一个表达式中,那么编译器不会进行任何优化。
- 当有连续构造时,后面是拷贝构造或者仍然是构造时,都会将其优化为一个直接构造。
- 当有连续拷贝构造时,会直接将其优化为一个拷贝构造。
- 当其最后是赋值重载时,编译器不会对其进行优化。
2.编程建议
-
传参的时候,尽量使用const加引用,这样可以减少很多拷贝,给编译器减少负担,并且还可以直接修改对象。但是需要注意传const对象时权限的放大问题。
-
在写返回值的时候,要尽量返回匿名对象,可以增加编译器的优化。
-
在接受返回值的时候,尽量使用拷贝构造的方式进行接收,这样可以直接进行优化,赋值重载的形式编译器不会进行优化。
创作不易,感谢阅读。