临时对象的来源
大家可能对这个临时对象这个概念还不是很清楚,那么首先我们花一些时间来理解临时对象。首先看下面一端代码:
1 #include <iostream> 2 void swap( int &a,int &b) 3 { 4 int temp; 5 temp=a; 6 a=b; 7 b=temp; 8 } 9 10 int main(int argc,char** argv) 11 { 12 int a=1,b=2; 13 swap(a,b); 14 std::cout<<a<<"-----"<<b<<std::endl; 15 return 0; 16 } 17 结果为 18 2-----1
可能大多数园友,认为"int temp"是"临时对象",但是其实不然,"int temp"仅仅是swap函数的局部变量。
临时对象是代码中看不到的,但是实际程序中确实存在的对象。临时对象是可以被编译器感知的。
为什么研究临时对象?
主要是为了提高程序的性能以及效率,因为临时对象的构造与析构对系统开销也是不小的,所以我们应该去了解它们,知道它们如何造成,从而尽可能去避免它们。
临时对象建立一个没有命名的非堆对象会产生临时对象。(不了解什么是堆对象和非堆对象,可以参考C++你最好不要做的这一博文,这里面有介绍。)这种未命名的对象通常在三种条件下产生:为了使函数成功调用而进行隐式类型转换时候、传递函数参数和函数返回对象时候。
那么首先看看为了使函数成功调用而进行隐式类型转换。
1 #include <iostream> 2 int countChar(const std::string & s,const char c) 3 { 4 int count=0; 5 for(int i=0;i<s.length( );i++) 6 { 7 if(*(s.c_str( )+i) == c) 8 { 9 count++; 10 } 11 } 12 return count; 13 } 14 15 int main(int argc,char** argv) 16 { 17 char buffer[200]; 18 char c; 19 std::cout<<"please input the string:"; 20 std::cin>>buffer; 21 std::cout<<"please input the char which you want to chount:"; 22 std::cin>>c; 23 int count=countChar(buffer,c); 24 std::count<<"the count is:"<<count<<std::endl; 25 return 0; 26 }
结果为:
这里调用函数countChar(const std::string& s,const char& c),那么我们看看这个函数的形参是const std::string &s,形参类型为const std::string,但是实际上传递的是char buffer[200]这个数组。其实这里编译器为了使函数调用成功做了类型转换,char *类型转换为了std::string类型,这个转换是通过一个赋值构造函数进行的,以buffer做为参数构建一个std::string类型的临时对象。当constChar返回时,即函数撤销,那么这个std::string临时对象也就释放了。但是其实从整个程序上来说临时对象的构造与释放是不必要的开销,我们可以提高代码的效率修改一下代码避免无所谓的转换。所以知道临时对象的来源,可以对程序性能上有一个小小提升。
注意仅当通过传值方式传递对象或者传递常量引用参数,才会发生这类型的转换,当传递非常量引用的参数对象就不会发生。因为传递非常量的引用参数的意图就是想通过函数来改变其传递参数的值,但是函数其实是改变的类型转换建立的临时对象,所以意图无法实现,编译器干脆就直接拒绝。
第二种情况是大家熟悉的函数传递参数的时候,会构造对应的临时对象。看下面一段代码运行的结果想必就一清二楚了。
1 #include<iostream> 2 class People 3 { 4 public: 5 People(std::string n,int a) 6 :name(n),age(a) 7 { 8 std::count<<"h2"<<std::endl; 9 } 10 People( ) 11 { 12 std::count<<"h1"<<std::endl; 13 } 14 People(const People& P) 15 { 16 name=p.name; 17 age=p.age; 18 std::cout<<"h3"<<std::endl; 19 } 20 std::string name; 21 int age; 22 }; 23 24 void swap(People p1,People p2) 25 { 26 People temp; 27 temp.age=p1.age; 28 temp.name=p1.name; 29 p1.age=p2.age; 30 p1.name=p2.name; 31 p2.age=temp.age; 32 p2.name=temp.name; 33 } 34 35 int main(int argc, char ** argv) 36 { 37 People p1("tom",18),p2("sam",19); 38 swap(p1,p2); 39 return 0; 40 }
结果为:
这里分析下前面两个"h2"是通过调用构造函数People(std::string n,int a)打印出来的,而"h3"就是通过调用复制构造函数People(const People&)而建立临时对象打印出来的,h1是调用默认构造函数People( )打印出来的。那么怎么避免临时对象的建立呢?很简单,我们通过引用实参而达到目的
1 void swap(People &p1,People &p2)
第三种情景就是函数返回对象时候。这里要注意临时对象的创建是通过复制构造函数构造出来的。
例如 const Rationanl operator+(Rationanl a,Rationanl b)该函数的返回值的临时的,因为它没有被命名,它只是函数的返回值。每回必须为调用add构造和释放这个对象而付出代价。
1 #include <iostream> 2 class Rationanl 3 { 4 public: 5 Rationanl(int e,int d) 6 :_elemem(e),_denom(d) 7 { 8 std::cout<<"h2"<<std::endl; 9 } 10 void show( ) const; 11 int elemem() const {return _elemem;} 12 int denom() const {return _denom;} 13 void setElemon(int e){_elemon=e;} 14 void setDenom(int d) {_denom=d;} 15 Rationanl(const Rationanl &r); 16 Rationanl & operator=(const Rationanl &r); 17 private: 18 int _elemem; 19 int _denom; 20 }; 21 Rationanl::Rationanl(const Rationanl &r) 22 { 23 setElemon(r.elemon( )); 24 setDenom(r.denom( ) ); 25 std::cout<<"h3"<<std::endl; 26 } 27 Rationanl & Rationanl::operator=(const Rationanl &r) 28 { 29 setElemon(r.elemon( )); 30 setDenom(r.denom( ) ); 31 std::cout<<"h4"<<std::endl; 32 return *this; 33 } 34 35 void Rationanl::show( ) 36 { 37 std::cout<<_elemen<<"/"<<_denom<<std::endl; 38 } 39 const Rationanl operator*(const Rationanl lhs,const Rationanl rhs) 40 { 41 return Rational result(lhs.elemen*rhs.elemen,rhs.denom*rhs.denom); 42 } 43 44 int main(int argc,char **argv) 45 { 46 Rationanl r1(1,2),r2(1,3) 47 Rationanl r3=r1*r2; //GCC做了优化,没有看到临时变量。编译器直接跳过建立r3,使用赋值符号 48 r3.show( ); 49 //相当于 (r1*r2).show( ); 50 return 0; 51 }
结果为:
这里很可惜没有看到我们想到看到的结果,结果应该为h2,h2,h2,h3,h4,应该是在返回值的时候有一个赋值构造函数,建立临时变量的,后来经笔者网上查找资料证实GCC做了优化。