Sales_item operator+(const Sales_item& lhs,const Sales_item& rhs)
{
Sales_item ret(lhs) ;
ret+= rhs ;
return ret ;
}
怎么可以返回一個局部變量呢??????
例如
Sales_item item1,item2,item3 ;
......(item2,item3初始化成員變量)
item1 = item2 + item3 ;
沒有問題,它返回的是一個值,而不是引用,所以是正確的。在item1 = item2 + item3 中發生了一次值拷貝(賦值),也就是將item2+item3返回的局部變量拷貝給了item1之后,局部變量的作用域結束
可以返回局部變量,但是不能返回局部變量的引用。理解區分值和引用這兩個概念是學習C++的一大關鍵,明白這兩個概念之后,你就會理解為什么C++的類里面需要有拷貝構造函數,賦值操作符,析構函數三個元素了以及其它的一些稀里古怪的用法和忠告了
--
Sales_item operator+(const Sales_item& lhs,const Sales_item& rhs)
{
Sales_item ret(lhs) ;
ret+= rhs ;
return ret ;
}
在return ret 時并不是簡單的返回這個局部變量,而是返回的是ret的一個副本temp,
temp是通過拷貝構造函數實現的 即 Sales_item temp(ret);也就是說在主函數中用的其實是個temp,而ret早在operator+調用完畢后就釋放了,而temp這個 對象一直在主函數中貯存(雖然顯示是看不到的 ) ????
嗯嗯,臨時對象一直是個有爭議的話題。temp只是臨時構造的,在賦值完畢之后它就析構了,不是一直在主函數中貯存的。這個臨時對象的作用域是什么???
在你提的這種情況下,其實編譯器是可以優化掉這個副本的,但是可惜C++標準只允許優化 “Sales_item item1=item2+item3” 這種情況的,也就是在拷貝構造這種情況下,編譯器都不再產生副本,而賦值還是不行的。也許在未來的C++標準由可能通過拓展語義來消除臨時對象,畢竟臨時對象成為影響C++效率的一個主要因素。
--
???
目前的C++標準不允許在賦值的時候優化掉函數返回的副本,也就是下面這兩種情況是不一樣的
//這是賦值給item1,目前的標準下是會產生副本 temp
Sales_item item1,item2,item3;
item1=item2+item3;
//這是拷貝構造item1,編譯器通常會優化(這個優化區別下面所說的NRV優化)掉副本,也就是不產生副本 temp
Sales_item item2,item3;
Sales_item item1=item2+item3;
看一下代碼中的TheFunctionTwo();
1 #include<iostream>
2 #include<string>
3
4 using namespace std;
5
6 class SimpleCat
7 {
8 public:
9 SimpleCat(void);
10 SimpleCat(int age,int weight);
11 SimpleCat(SimpleCat &rsc);
12 SimpleCat & operator=(const SimpleCat &rhs);
13 int GetAge(){return itsAge;}
14 int GetWeight(){return itsWeight;}
15 void SetAge(int age){itsAge = age;}
16
17 private:
18 int itsAge;
19 int itsWeight;
20 //int itsAge=5;//1>.\SimpleCat.cpp(14) : error C2864: “SimpleCat::itsAge”: 只有靜態常量整型數據成員才可以在類中初始化
21
22 public:
23 virtual ~SimpleCat(void);
24 };
25
26 SimpleCat::SimpleCat(void)
27 {
28 cout<<"SimpleCat constructor "<<this<<endl;
29 }
30
31 SimpleCat::SimpleCat(int age,int weight)
32 {
33 cout<<"SimpleCat constructor "<<this<<endl;
34 itsAge = age;
35 itsWeight = weight;
36 }
37
38 SimpleCat::SimpleCat(SimpleCat &rhs)
39 {
40 cout<<"SimpleCat copy constructor"<<this<<" 從 "<<&rhs<<"拷貝"<<endl;
41 itsAge = rhs.itsAge;
42 itsWeight = rhs.itsWeight;
43 }
44
45 SimpleCat &SimpleCat::operator=(const SimpleCat &rhs)
46 {
47 cout<<"SimpleCat重載賦值運算符"<<this<<" 從 "<<&rhs<<"賦值"<<endl;
48 itsAge = rhs.itsAge;
49 itsWeight = rhs.itsWeight;
50 return *this;
51 }
52
53 SimpleCat::~SimpleCat(void)
54 {
55 cout<<"SimpleCat destructor"<<this<<endl;
56 }
57
58 SimpleCat &TheFunction();
59
60 SimpleCat TheFunctionTwo();
61
62 SimpleCat &TheFunctionThree();
63
64 void TheFunctionFour(SimpleCat simpleCat);
65
66 int main()
67 {
68 SimpleCat myCat;
69 cout<<myCat.GetAge()<<endl;//這個值區別于java不是賦初值為0的,而是一個隨機的值
70 // cout<<myCat.itsAge<<endl;
71
72 cout<<"------------------------"<<endl;
73 SimpleCat &rCat = TheFunction();
74 int age = rCat.GetAge();
75 cout<<"rCat is "<<age<<"yeas old!"<<endl;
76 cout<<"&rCat: "<<&rCat<<endl;
77 SimpleCat *pCat = &rCat;
78 //delete rCat;//不能對引用使用delete
79 delete pCat;
80 //delete好像沒有釋放內存,怎么獲取的還是原來的值
81 //可能在這個內存區域存放的還是原來的?先new string后再調用也沒變,與編譯器有關還是什么??
82 for(int i =0;i<10;i++)
83 {
84 //想通過創建string對象來填充之前的內存區間,好像沒用
85 string *s = new string("abcdefghijklmn");
86 }
87
88 //這時問題來了,rCat.getAge()為123了
89 SimpleCat *pSecond = new SimpleCat(123,444);
90
91 cout<<"delete pCat后再使用rCat引用會發生什么問題???"<<endl;
92 cout<<"delete pCat后 &rCat"<<&rCat<<endl;
93 cout<<"delete pCat后 rCat.age"<<rCat.GetAge()<<endl;
94
95 cout<<"--------------------------"<<endl;
96 SimpleCat myCat2 = TheFunction();//這個會發生內存泄漏,在函數中申請的內存會得不到釋放
97 //myCat2是通過默認的拷貝函數來進行初始化的
98 cout<<"myCat2的地址是 "<<&myCat2<<endl;
99 cout<<endl<<"---------------------------"<<endl;
100
101 //直接的調用默認拷貝構造函數的
102 //cout<<"直接的調用默認拷貝構造函數的,這個語句一共創建了多少個對象"<<endl;
103 //SimpleCat myCat3 = TheFunctionTwo();//這個盡然沒調用拷貝構造函數?????
104 //cout<<"myCat3的地址是 "<<&myCat3<<endl;
105 //cout<<"myCat3.GetAge() "<<myCat3.GetAge()<<endl;
106
107 //調用默認的賦值運算符的
108 //cout<<"調用默認的賦值運算符的,這個語句一共創建了多少個對象"<<endl;
109 //SimpleCat myCat4;
110 //myCat4 = TheFunctionTwo();
111 //cout<<"myCat4.GetAge() "<<myCat4.GetAge()<<endl;
112
113
114 //這種調用的方式
115 //cout<<"TheFunctionTwo()返回的臨時對象賦給一個引用"<<endl;
116 //SimpleCat &rmyCat = TheFunctionTwo();
117 //cout<<"打印一下返回的臨時對象的地址,臨時對象賦給引用之后就不會立即析構了---"<<&rmyCat<<endl;
118 //cout<<"rmCat.GetAge() "<<rmyCat.GetAge()<<endl;
119
120 SimpleCat myCat5;
121 TheFunctionFour(myCat5);
122
123 return 0;
124 }
125
126
127 //這個函數在返回時,是否會創建一個臨時引用變量???
128 //TheFunctionTwo(SimpleCat &simpleCat)這個函數在傳遞參數時是否會創建一個臨時的引用變量
129 //臨時的引用變量的作用域范圍是什么
130 SimpleCat &TheFunction()
131 {
132 SimpleCat *pFrisky = new SimpleCat(5,9);
133 cout<<"pFrisky: "<<pFrisky<<endl;
134 return *pFrisky;
135 }
136
137
138
139 //我要看對象被創建了幾次,是否創建臨時對象
140 SimpleCat TheFunctionTwo()
141 {
142 SimpleCat tempCat;
143 cout<<"in TheFunctionTwo tempCat指針 "<<&tempCat<<endl;
144 tempCat.SetAge(9999);
145 //返回時是否還創建一個臨時對象
146 return tempCat;
147
148 }
149
150
151 //這種方式肯定是錯的,返回了局部變量的引用
152 SimpleCat &TheFunctionThree()
153 {
154 SimpleCat tempCat;//局部變量
155 cout<<"in TheFunctionThree tempCat指針 "<<&tempCat<<endl;
156 tempCat.SetAge(9999);
157 //返回時是否還創建一個臨時對象,已引用的形式
158 return tempCat;
159 }
160
161
162
163 //我要看再實參對形參進行拷貝構造時,是否會打印出調用的拷貝函數信息
164 void TheFunctionFour(SimpleCat simpleCat)
165 {
166 cout<<"形式參數simpleCat的地址 "<<&simpleCat<<endl;
167
168 }
169
170
171
------------------------------------------------------------------------------------------------------------------------------
先說說第一塊吧,
這個貌似應該調用拷貝構造的地方沒有調用拷貝構造,應該是編譯器做的優化,可以參考《深入探索C++對象模型》P66頁。
按照書中的說法,很可能只創建了1個對象
X bar()
{
X xx;
return xx;
}
可能會被編譯器優化成
void bar(X & _result)
{
_result.X::X();
return;
}
所以調用X a = bar(),其實被轉換成 bar(X& a);
所以一個本該調用拷貝構造的地方卻很可能調僅用了構造函數來完成工作。
這個問題我也沒有搞明白,我沒有試過lz的代碼是不是會這樣,如果真這樣的話,我覺得編譯器就管的太多了,如果拷貝構造中有一些特殊的功能呢(就像樓主有個輸出語句),豈不是無聲無息中被抹殺了。這個功能被稱為NRV,好像一直沒有人對這個問題給出非常明確的回答
------------------------------------------------------------------------------------------------------------------------------
thx,在vs2005中代碼優化開啟時,第一塊確實只會調一個構造函數
把代碼優化禁用后,就會調用拷貝構造函數了
確實是個NRV的問題
http://blog.vckbase.com/bruceteen/archive/2005/12/30/16652.html
------------------------------------------------------------------------------------------------------------------------------
正如4樓的所說~~~
到底創建多少臨時對象,要視編譯器而定,看編譯器是否采用NVR...
(其實一般情況下,NVR都是未采用的~),因此對后三個注釋代碼做
如下分析:寫出編譯后的偽代碼(僅供參考)
編譯器更改后的函數原型void TheFunctionTwo(SimpleCat&)
注釋1:
SimpleCat myCat3;
TheFunctionTwo(myCat3)
{
SimpleCat tempCat;
templCat.SimpleCat::SimpleCat();
cout<<"in TheFunctionTwo tempCat指針 "<<&tempCat<<endl;
tempCat.SetAge(9999);
myCat3.SimpleCat::SimpleCat(tempCat);
tempCat.SimpleCat::~SimpleCat();
}
注釋2:
SimpleCat myCat4;
myCat4.SimpleCat::Simple();
SimpleCat temp;
TheFunctionTwo(temp)
{
SimpleCat tempCat;
templCat.SimpleCat::SimpleCat();
cout<<"in TheFunctionTwo tempCat指針 "<<&tempCat<<endl;
tempCat.SetAge(9999);
temp.SimpleCat::SimpleCat(tempCat);
tempCat.SimpleCat::~SimpleCat();
}
myCat4.operator=(temp);
temp.SimpleCat::~SimpleCat();
注釋3:
//SimpleCat &rmyCat = TheFunctionTwo();
SimpleCat temp;
SimpleCat &rmyCat=temp;
TheFunctionTwo(temp)
{
SimpleCat tempCat;
templCat.SimpleCat::SimpleCat();
cout<<"in TheFunctionTwo tempCat指針 "<<&tempCat<<endl;
tempCat.SetAge(9999);
temp.SimpleCat::SimpleCat(tempCat);
tempCat.SimpleCat::~SimpleCat();
}
這樣就知道到底需要多少臨時對象了~~~~
posted on 2009-05-11 15:22
Frank_Fang 閱讀(1372)
評論(4) 編輯 收藏 所屬分類:
C++編程