印象中,函數調用的時候,參數past by value、返回值return by value,總是伴隨着對象的複製。
實際上參數傳遞是這樣的,但是返回值有可能不是這樣的(雖然大部分都是面臨拷貝構造函數的調用),這取決於編譯器。
#include<string>
#include<list>
#include<iostream>
using namespace std;
class C
{
public:
C()
{
cout<<"C default constructor(),this="<<this<<endl;
}
C(const C &c)
{
cout<<"C const copy constructor(),this="<<this<<",reference="<<&c<<endl;
}
C(C &c)
{
cout<<"C nonconst copy constructor(),this="<<this<<",reference="<<&c<<endl;
}
const C & operator=(const C &c)
{
cout<<"C assignment(),this="<<this<<endl;
return *this;
}
~C()
{
cout<<"C destructor(),this="<<this<<endl;
}
};
C test_1(int i)
{
cout<<"entering test_1"<<endl;
C x;
C a; //a會析構
cout<<"leaving test_1"<<endl;
return x; //return之後棧不清空,x不會析構,即使編譯器已經將優化設置成-O0
}
C test_2(int i)
{
cout<<"entering test_2"<<endl;
C x;
C a;
cout<<"leaving test_2"<<endl;
if(i>0)
return x;
else
return a; //x和a都會析構,返回的時候,先調用拷貝構造函數,初始化返回值(此處爲main裏面的z),
//然後再析構a和x
}
C test_3(C t)
{
return t; //此處導致t的構造和析構
}
C test_4(C t)
{
C x=t;
return x; //此處導致t的構造和析構,但是x只會構造不會析構
}
int main()
{
cout<<"invoking test_1"<<endl;
C y=test_1(1); //這種調用不會有拷貝構造函數,y直接爲test_1函數棧裏面生成的對象,編譯器優化的結果
cout<<"end invoke test_1"<<endl;
cout<<"================華麗分割線================="<<endl;
cout<<"invoking test_2"<<endl;
C z=test_2(1); //這種情況會調用拷貝構造函數(nonconst版本),初始化z
cout<<"end invoke test_2"<<endl;
cout<<"================華麗分割線================="<<endl;
cout<<"invoking test_3"<<endl;
C a=test_3(y);
cout<<"end invoke test_3"<<endl;
cout<<"================華麗分割線================="<<endl;
cout<<"invoking test_4"<<endl;
C b=test_4(y);
cout<<"end invoke test_4"<<endl;
cout<<"================華麗分割線================="<<endl;
cout<<"開始測試臨時變量何時析構"<<endl;
test_2(1), //(注意結束處是逗號)此處返回的C沒有指定任何變量,編譯器會生成臨時變量
cout<<"結束測試臨時變量何時析構"<<endl;//臨時變量會再語句的第一個分號處析構,cout完成之後析構
cout<<"================華麗分割線================="<<endl;
cout<<"開始測試臨時變量何時析構"<<endl;
test_2(1); //(注意結束處是分號)此處返回的C沒有指定任何變量,編譯器會生成臨時變量
cout<<"結束測試臨時變量何時析構"<<endl;//臨時變量會再語句的第一個分號處析構,cout開始之前析構
cout<<"================華麗分割線================="<<endl;
cout<<"================下面開始析構棧裏面的變量了,啦啦啦================="<<endl;
cout<<"================析構順序按照入棧的順序,後進先出,後構造,先析構==========="<<endl;
return 0;
}
運行結果:
AlexdeMacBook-Pro:~ alex$ a.out
invoking test_1
entering test_1
C default constructor(),this=0x7fff5929baa8
C default constructor(),this=0x7fff5929b8d8
leaving test_1
C destructor(),this=0x7fff5929b8d8
end invoke test_1
================華麗分割線=================
invoking test_2
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba98,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
end invoke test_2
================華麗分割線=================
invoking test_3
C nonconst copy constructor(),this=0x7fff5929ba88,reference=0x7fff5929baa8
C nonconst copy constructor(),this=0x7fff5929ba90,reference=0x7fff5929ba88
C destructor(),this=0x7fff5929ba88
end invoke test_3
================華麗分割線=================
invoking test_4
C nonconst copy constructor(),this=0x7fff5929ba78,reference=0x7fff5929baa8
C nonconst copy constructor(),this=0x7fff5929ba80,reference=0x7fff5929ba78
C destructor(),this=0x7fff5929ba78
end invoke test_4
================華麗分割線=================
開始測試臨時變量何時析構
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba70,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
結束測試臨時變量何時析構
C destructor(),this=0x7fff5929ba70
================華麗分割線=================
開始測試臨時變量何時析構
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba68,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
C destructor(),this=0x7fff5929ba68
結束測試臨時變量何時析構
================華麗分割線=================
================下面開始析構棧裏面的變量了,啦啦啦=================
================析構順序按照入棧的順序,後進先出,後構造,先析構===========
C destructor(),this=0x7fff5929ba80
C destructor(),this=0x7fff5929ba90
C destructor(),this=0x7fff5929ba98
C destructor(),this=0x7fff5929baa8
AlexdeMacBook-Pro:~ alex$
AlexdeMacBook-Pro:~ alex$
結論:
一:return by value時候編譯器的優化
編譯器在能夠做優化的時候,會盡量幫你做優化,比如test_1,總是將棧裏面的x直接給調用者,避免了多一次的析構和構造。即使在關閉編譯器優化的時候,它依然給你做了這個動作。但是在test_2裏面,返回值是動態的,隨着參數變動而變動,編譯器是沒有辦法得知保留哪個的,於是索性都析構了。
在Effective C++ Item 21,page 94, Don't try to return a reference when you must return an object.
作者說:C++和所有編程語言一樣,允許編譯器實現者施行最優化,用以改善產出碼的效率卻不改變其可觀察的行爲。
g++確實對此做了優化,但是動態返回值,編譯器卻無能爲力。你無法要求編譯器按照每個分支,生成不同的代碼。否則在複雜的程序下,生成的可執行文件大小那將無法估計了
二:臨時變了的析構時機
臨時變量,總是在執行完生成臨時變量的那一行代碼之後析構
(是不是比較拗口?)
那就這樣說吧:生成臨時變量之後,遇到第一個分號,析構函數開始調用