先看下面一段代码:
class CRet
{
};
class CTest
{
public:
CRet Get1()
{
return m_objCRet;
}
CRet& Get2()
{
return m_objCRet;
}
private:
CRet m_objCRet;
};
int main(int argc, char* argv[])
{
CTest objCTest; // a:调用构造函数
objCTest.Get1(); // b:调用拷贝构造函数、析构函数
objCTest.Get2(); // c:不调用
CRet objCRet1 = objCTest.Get1(); // d:调用拷贝构造函数; objCRet1拷贝了一份m_objCRet
CRet& objCRet2 = objCTest.Get1(); // e:调用调用拷贝构造函数; objCRet2拷贝了一份m_objCRet
CRet objCRet3 = objCTest.Get2(); // f:调用拷贝构造函数; objCRet3拷贝了一份m_objCRet
CRet& objCRet4 = objCTest.Get2(); // g:不调用; objCRet4引用了一份m_objCRet
return 0; // h:调用4次析构函数
}
上述代码中对比了返回值是否引用、接受值是否引用。下面对每个语句进行解释。
a: 这个无需多讲。
b: Get1返回时,系统会产生一个CRet临时对象,当然这个临时对象我们看不到。但它的确存在,而且还会调用拷贝构造函数。然而,这个临时对象却没变量来接收,系统当然不会允许这个临时对象继续存在,于是就立即把它析构掉。
c: Get2返回的是引用,没有临时对象的产生。所以这里没有调用拷贝构造函数和析构函数。
d: Get1很荣幸,因为它返回的值由objCRet1通过调用拷贝构造函数来接收。
e: Get1返回时,系统产生了CRet临时对象,所以会调用拷贝构造函数。然而系统不能将该临时对象析构,因为objCRet2引用了它。
f: 这个情况与d的相似,由objCRet3通过调用拷贝构造函数来接收返回值。
g: Get2返回的是引用,该返回值还继续被objCRet4引用,所以不会调用(拷贝)构造函数。但此时objCRet4与m_objCRet是同一份。注意了,修改objCRet4就会导致m_objCRet的改变。
h: 这里也无需多讲。
延伸阅读:临时对象
临时对象不是平时在函数体内看到的临时变量。它不出现在源代码中,建立一个没有命名的非堆(non-head)对象时会产生临时对象。这种未命名的对象通常会在两种条件下产生:为了使函数调用而进行隐式类型转换以及函数返回对象时。