在看C++Primer p214---返回非引用类型,有一句话“函数的返回值用于初始化在调用函数处创建的临时对象。”
C++编程思想对临时对象的定义.“有时候,在求表达式值期间,编译器必须建立临时对象。像其他任何对象一样,它们需要存储空间而且必须被构造和删除。区别是我们从来看不到它们---编译器负责决定它们的去留以及存在的细节。这里有一个关于临时变量的情况:它们自动地成为常量。因为我们通常接触不到临时对象,不能使用与之相关的信息,所以告诉临时对象做一些改变几乎肯定会出错。”
之前看书的两个问题视乎有点明白了:
1. 前置操作返回对象本身,这是左值;后置操作返回的则是右值。
int i = 0, j;
j = ++i; //j=1,i=1
j = i++; //j=1,i=2
后置操作符会产生临时对象来保存操作数原来的值,而临时对象只能作为右值,不能被修改。
2. P52
观察引用绑定到不同的类型时所发生的事情,例如:
double dval = 3.14;
const int& ri = dval;
编译器会把这些代码转换成如下形式的编码:
int temp = dval; //creat temporary int from the double
const int& ri = temp; //bind ri to that temporary
对于不可寻址的值,如文字常量以及不同类型的对象,const引用会产生临时副本,引用实际指向该副本。
如果ri不是const,那么可以给ri赋一新值。这样做不会修改dval,而是修改了temp。期望对ri的赋值会修改dval的程序员会发现dval并没有被修改。仅允许const引用绑定到需要临时使用的值完全避免了这个问题。
注意:非const引用只能绑定到改引用同类型的对象。
const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
下面摘自More Effective C++
未命名对象在两种条件下产生:(理解这些临时对象很重要,因为构造和释放它们的开销对于程序的性能来说有着不可忽视的影响。)
1. 为了使函数成功调用而进行隐式类型转换
当传送给函数的对象类型与参数类型不匹配时会产生这种情况。例如一个函数,计算一个字符在字符串中出现的次数:
size_t countChar(const string& str, char ch);
char buffer[MAX_STRING_LEN];
char c;
cin >> c >> setw(MAX_STRING_LEN) >> buffer;
cout << "There are" << countChar(buffer, c)
<< "occurrences of the character" << c
<< "in" << buffer << endl;
第一个被传送的参数是字符数组,但对应函数的正被绑定的参数类型是const string&。编译器建立一个string类型的临时对象,通过以buffer做为参数调用string的构造函数来初始化这个临时对象。coutChar的参数str被绑定在这个临时的string对象上。当countChar返回时,临时对象自动释放。
注:这种类型转换造成临时string对象的构造和释放是不必要的开销。有两种方法。
注:仅当通过传真方式传递对象或传递常量引用参数时,才会发生这些类型转换。当传递一个非常量引用参数对象就不会发生。为什么?不太理解?
void uppercasify_1(string& str) //把str中所有的字符改成大写
{
}
char subtleBookPlug[] = "Effective C++";
uppercasify_1(subtleBookPlug); //非常量引用不能发生类型转换(临时对象)
2.函数返回对象时。
class Rational
{
public:
Rational(int numerator = 0, int denominator = 1);
int numerator()const;
int denominator()const;
const Rational operator*(const Rational& rhs)const;
private:
};
int main()
{
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * 2; ///可以 result=oneHalf.operator*(2)
result = 2 * oneHalf; ///错误 result=2*operator*(oneHalf)
return 0;
}
对于result=oneHalf * 2,这里发生了隐士转换,
const Rational temp(2); //以2产生一个暂时的Rational对象
result = oneHalf * temp;//
当然这些临时对象都是开销的,而更重要的是训练自己寻找可能建立临时对象的地方。在任何时候只要见到常量引用参数,就存在建立临时对象而绑定在参数上的可能性。在任何时候只要见到函数返回对象就会有一个临时对象被建立(以后被释放)。
3. 返回值的优化
当函数返回对象时,建立临时对象会调用构造函数,析构函数,复制构造函数,而采用返回值优化可以只调用一个构造函数。(注:gcc编译已做了优化)
class Rational
{
public:
Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator)
{
cout << "Constructor Called..." << endl;
}
~Rational()
{
cout << "Destructor Called..." << endl;
}
Rational(const Rational& rhs)
{
this->d = rhs.d;
this->n = rhs.n;
cout << "Copy Constructor Called..." << endl;
}
int numerator() const { return n; }
int denominator() const { return d; }
private:
int n, d;
};
//gcc下,编译器自动优化了,只调用了一次构造函数
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
cout << "----------- Enter operator* -----------" << endl;
Rational tmp(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
cout << "----------- Leave operator* -----------" << endl;
return tmp;
}
//返回值优化版本
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
int main(int argc, char** argv)
{
Rational x(1, 5), y(2, 9);
Rational z = x * y;
cout << "calc result: " << z.numerator()
<< "/" << z.denominator() << endl;
return 0;
}
“C++规则允许编译器优化不出现的临时对象。编译器就会被允许消除在operator*内的临时变量和operator*返回的临时变量。它们能在为目标c分配的内存里构造return表达式定义的对象。如果你的编译器这样去做,调用operator*的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函数---建立c时调用的构造函数。”