类和对象——拷贝对象时的一些编译器优化

打印 上一主题 下一主题

主题 887|帖子 887|积分 2661

拷贝对象时的一些编译器优化

在传参和传返回值的过程中,一般编译器会做一些优化(也就是说有的不做优化),减少对象的拷贝,这个在一些场景下照旧非常有效的。
这里只举几个案例,详细见书籍《深度探索c++对象模型》。
在20世纪末盛行的编译器(比方,vc++6.0)不会对这种情况举行优化。
案例1:仅使用类中的成员函数

很多时候,生成这个对象的目的仅仅是为了调用类中的某个函数。此时没须要生成一个对象,特殊是生成一个对象作为实参上传给平凡函数。
  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         void print() {
  25.                 using std::cout;
  26.                 cout << a << "\n";
  27.         }
  28. private:
  29.         int a;
  30. };
  31. //若调用拷贝构造仅仅是为了调用这个函数,完全没必要传值传参
  32. void f1_1(A a) {
  33.         a.print();
  34. }
  35. //所以直接加引用
  36. void f1_2(A& a) {
  37.         a.print();
  38. }
  39. void f1() {
  40.         A a;
  41.         f1_1(a);
  42.         cout << endl;
  43.         f1_2(a);
  44.         cout << endl;
  45. }
  46. int main() {
  47.         f1();
  48.         return 0;
  49. }
复制代码
案例2:案例1减少一次拷贝构造

首先,const对象不能调用非const成员函数。所以const对象也要准备对应的const函数重载。
其次,引用和const一般在一起,为了避免别名修改原来的对象(变量)。
最后,形参使用引用可以减少一次拷贝构造。
  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. //为了支持生成临时对象,使用const引用
  33. void f2_1(const A& a) {
  34.         a.print();
  35. }
  36. void f2_2(A& a) {
  37.         a.print();
  38. }
  39. void f2_3(A& a) {//非const形参,不具有常属性
  40.         a.print();
  41. }
  42. void f2() {
  43.         A a;
  44.         f2_1(a);//权限缩小
  45.         cout << endl;
  46.         f2_2(a);//权限平移
  47.         cout << endl;
  48.         //f2_3(A());//权限放大
  49.         f2_1(A());//形参也具有常属性时权限平移,可以调用
  50.         cout << endl;
  51. }
  52. int main() {
  53.         f2();
  54.         return 0;
  55. }
复制代码
输出:
  1. A(int a)
  2. 6
  3. 6
  4. A(int a)
  5. 6
  6. ~A()
  7. ~A()
复制代码
f2_3(A());无法编译通过,因为临时对象、匿名对象都有常属性,上传无常属性形参的函数,权限放大。
案例3:临时对象也具有常属性

在案例2已经证明匿名对象具有常属性。隐式范例转换的临时对象也具有常属性。
  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. //const引用能很好的支持生成临时对象
  33. void f3_1(const A& a) {//这个地方引用和const一般同时出现防止不小心修改
  34.         a.print();
  35. }
  36. void f3() {//少调用一次拷贝构造
  37.         f3_1(A());//匿名对象有常属性
  38.         cout << endl;
  39.         f3_1(A(4));
  40.         cout << endl;
  41.         f3_1(3);//临时对象也具有常属性
  42.         cout << endl;
  43. }
  44. int main() {
  45.         f3();
  46.         return 0;
  47. }
复制代码
输出:
  1. A(int a)
  2. 6
  3. ~A()
  4. A(int a)
  5. 4
  6. ~A()
  7. A(int a)
  8. 3
  9. ~A()
复制代码
它们都被优化成了只调用一次构造函数。
案例4:const引用延伸生命周期

const引用可以延伸临时对象的生命周期,本质是将临时对象变成著名对象,如许临时对象就可以像著名对象一样生命周期在局部。
  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. //缺省值为匿名对象
  33. //const延长生命周期使得匿名对象存在于局部
  34. void f4_1(const A& a = A()) {
  35.         a.print();
  36. }
  37. void f4() {
  38.         f4_1();
  39.         cout << endl;
  40.         //这里只有ref出了作用域,
  41.         //临时对象的生命周期才终止
  42.         const A& ref = A();
  43.         cout << endl;
  44.         ref.print();//还在{}也就是作用域内,可以使用
  45.         cout << endl;
  46. }
  47. int main() {
  48.         f4();
  49.         return 0;
  50. }
复制代码
案例5:传匿名对象传参

编译器优化情况1:隐式范例转换作为实参,此时会调用两次构造。编译器将连续的两次构造(构造+拷贝构造)优化为直接构造。
c++尺度并没有对这种情况举行优化说明,这个实在照旧编译器自己的举动。在一些年代比较久远的编译器(比如20世纪末)就不会。
  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. void f5_1(A a) {
  33.         a.print();
  34. }//析构
  35. void f5_2(const A a) {
  36.         a.print();
  37. }
  38. A f5_3() {
  39.         A a;
  40.         return a;
  41. }
  42. //隐式类型,连续构造(两次及以上)->优化为直接构造
  43. void f5() {
  44.         //传值传参
  45.         //正常情况
  46.         A a;//构造
  47.         f5_1(a);//拷贝构造
  48.         cout << endl;
  49.         // 一个表达式中,构造+拷贝构造->优化为一个构造
  50.         f5_1(A());//匿名对象构造+拷贝构造被优化
  51.         cout << endl;
  52.         f5_1(A(3));
  53.         cout << endl;
  54.        
  55.         f5_1(4);//隐式类型转换
  56.         cout << endl;
  57.         //这个也是构造+拷贝构造
  58.         A b = A(3);
  59.         cout << endl;
  60. }
  61. int main() {
  62.         f5();
  63.         return 0;
  64. }
复制代码
输出:
  1. A(int a)
  2. A(const A& aa)
  3. 6
  4. ~A()
  5. A(int a)
  6. 6
  7. ~A()
  8. A(int a)
  9. 3
  10. ~A()
  11. A(int a)
  12. 4
  13. ~A()
  14. A(int a)
  15. ~A()
  16. ~A()
复制代码
分析:
f5_1(A());,f5_1(A(3));:匿名对象调用构造函数,加拷贝构造生成形参。
f5_1(4);:隐式转换,一次构造加拷贝构造。
A b = A(3);:一次构造加拷贝构造。
这三种情况,都被优化为一次构造。
案例6:函数传值返回时的优化

  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. A f6_1() {
  33.         A a;//构造
  34.         return a;//拷贝构造生成临时对象
  35. }
  36. A& f6_2() {
  37.         A a;
  38.         return a;
  39. }
  40. void f6() {
  41.         A a;
  42.         cout << endl;
  43.         f6_1();
  44.         cout << endl;
  45.         a = f6_1();
  46.         cout << endl;
  47.        
  48.         A ret = f6_1();
  49.         cout << endl;
  50.         A ret2 = f6_2();
  51.         cout << endl;
  52. }
  53. int main() {
  54.         f6();
  55.         return 0;
  56. }
复制代码
输出:
  1. A(int a)
  2. A(int a)
  3. A(const A& aa)
  4. ~A()
  5. ~A()
  6. A(int a)
  7. A(const A& aa)
  8. ~A()
  9. A& operator=(const A& aa)
  10. ~A()
  11. A(int a)
  12. A(const A& aa)
  13. ~A()
  14. A(int a)
  15. ~A()
  16. A(const A& aa)
  17. ~A()
  18. ~A()
  19. ~A()
复制代码
单独看A ret = f6_1();这种情况:

A f6_1()在return语句会生成临时对象,但编译器举行了优化,直接将这个a在生命周期结束前拷贝给ret。
所以在一个表达式的连续两个步调里,局部对象构造 + 传值返回生成临时对象调用拷贝构造,两次调用构造被优化为一次。
而A ret2 = f6_2();因为f6_2是传引用返回,所以直接省去了return语句的一次拷贝构造,在析构前生成临时对象,之后通过拷贝构造将对象拷贝给ret2。
案例7:优化的条件

  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. A f7_1() {
  33.         A a;
  34.         return a;
  35. }
  36. void f7() {//这种情况编译器不会再优化
  37.         A ret2;
  38.         ret2 = f7_1();
  39. }
  40. int main() {
  41.         f7();
  42.         return 0;
  43. }
复制代码
f7()这种情况不能优化,两个缘故原由:


  • 同范例才气优化(都是构造或都是拷贝构造才气优化,这里是构造和赋值)。
  • 不在同一步调(声明对象和赋值重载是两个语句或者说步调)。
案例8:隐式范例转换的优化

和案例6的情况相似,都是构造临时对象并返回,只是存在隐式范例转换。所以被优化为一次构造。
  1. #include<iostream>
  2. #include<cstdlib>
  3. using namespace std;
  4. class A {
  5. public:
  6.         A(int a = 6)
  7.                 :a(a) {
  8.                 cout << "A(int a)" << endl;
  9.         }
  10.         A(const A& aa)
  11.                 :a(aa.a) {
  12.                 cout << "A(const A& aa)" << endl;
  13.         }
  14.         A& operator=(const A& aa) {
  15.                 cout << "A& operator=(const A& aa)" << endl;
  16.                 if (this != &aa) {
  17.                         a = aa.a;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~A() {
  22.                 cout << "~A()" << endl;
  23.         }
  24.         //相应函数也要对这个类的成员函数进行限制防止权限放大
  25.         void print() const {
  26.                 using std::cout;
  27.                 cout << a << "\n";
  28.         }
  29. private:
  30.         int a;
  31. };
  32. //被优化为直接构造
  33. //构造匿名对象加临时对象,两次构造被优化为1次
  34. A f8_1() {
  35.         return A();
  36. }
  37. A f8_2() {
  38.         return 8;
  39. }
  40. A f8_3() {
  41.         return A(1);
  42. }
  43. void f8() {
  44.         A a1 = f8_1();
  45.         cout << endl;
  46.         A a2 = f8_2();//隐式类型转换
  47.         cout << endl;
  48.         A a3 = f8_3();
  49.         cout << endl;
  50. }
  51. int main() {
  52.         f8();
  53.         return 0;
  54. }
复制代码
所以就有了如许一个特性:局部对象都只能传值返回,因此可以的话尽大概使用临时对象返回或隐式范例转换,可以减少拷贝调用次数。
再次理解封装

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体举行描述,然后通过编写步伐,创建对象后计算机才可以认识。
比如想要让计算机认识洗衣机,就需要:

  • 用户先要对现实中洗衣机实体举行抽象——即在人为头脑层面对洗衣机举行认识,洗衣机有什么属性,有那些功能,即对洗衣机举行抽象认知的一个过程。
  • 经过1之后,在人的头脑中已经对洗衣机有了一个苏醒的认识,只不外此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面向对象的语言(比如:c++、java、python等)将洗衣机用类来举行描述,并输入到计算机中。
  • 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象举行描述的,通过洗衣机类,可以实例化出一个个详细的洗衣机对象,此时计算机才气洗衣机是什么东西。
  • 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。
所以类是对某一类实体对象来举行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义范例,才用该自定义范例就可以实例化详细的对象。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

熊熊出没

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表