前言
在C++中很容易就寫出一些代碼,這些代碼的特點就是偷偷的給你產生了一些臨時物件,導致臨時物件會呼叫拷貝建構式,賦值運算子,解構式,假如該物件還有繼承的話,也會呼叫父類的拷貝建構式,賦值運算賦函式等,這些臨時物件所呼叫的函式,都是不必要的開銷,也就是說,我本意不想你給我呼叫這些函式的,但你編譯器卻給我偷偷的呼叫了,就是由于我程式員寫代碼產生臨時物件而產生的,
所以臨時物件產生的話題也應運而生,這篇文章主要是探討常見的臨時物件產生的情況,及其如何避免和解決這種臨時物件產生的方式,
文章目錄
- 前言
- 1. 以值傳遞的方式給函式傳參
- 驗證臨時物件的而外開銷(1)
- 解決方案
- 2. 型別轉換成臨時物件 / 隱式型別轉換保證函式呼叫成功
- 驗證臨時物件的而外開銷(2)
- 解決方案
- 3. 函式回傳物件時候
- 驗證臨時物件的而外開銷(3)
- 解決方案
1. 以值傳遞的方式給函式傳參
這種是最常見的產生嶺師物件的方式了,
以值傳遞的方式給函式傳參這種方式會直接呼叫物件的拷貝建構式,生成一個臨時物件傳參給函式,當臨時物件銷毀時候,也是函式形參銷毀,也是函式執行完后,就會呼叫該臨時物件的解構式,此時,無論是呼叫拷貝建構式和解構式,都是額外的開銷,
(驗證是否呼叫拷貝建構式和解構式,可以在書寫拷貝建構式和解構式驗證)
(驗證是否為臨時物件可以通過再函式內部修改形參的值,在函式外部列印看看是否修改成功)
驗證臨時物件的而外開銷(1)
# include<iostream>
using namespace std;
class Person{
public:
Preson()
{
cout << "無參建構式!" << endl;
}
Person(int a)
{
m_age = a;
cout << "有參建構式!" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "拷貝建構式!" << endl;
}
~Person()
{
cout << "解構式!" << endl;
}
int fun(Person p) //普通的成員函式,注意引數是以值的方式呼叫的
{
p.m_age = 20; //這里修改對外界沒有印象
return p.m_age;
}
int m_age;
};
int main()
{
Person p(10);//初始化
p.fun(p);
return 0;
}
先來預測一下呼叫函式的次數:也就是我們本意想呼叫的方式:
會執行一次 Person的有參建構式;
會執行一次Person的解構式;
于此同時我們看看,編譯結果實際情況:

和我們預期并不一樣!!! 多了一次拷貝建構式和一次解構式,這兩個函式并不是我們希望要得,或者說,這個多余函式開銷是不必要的;
產生的原因也很好理解:
由于 fun成員函式里面的形參是Person p,這樣會導致在呼叫這個fun函式時候,會傳遞過去的是實參的復制品,臨時物件,并不是外面main函式的實參,這里可以在fun函式里修改一樣形參就可以發現,外面的實參沒發生改變,
所以產生的臨時物件給形參傳參時候,在我們看來類似 Person p = p;實際上是Person p = temp;而這句 Person p = temp;就會發生拷貝建構式啦,于此同時 fun函式呼叫結束后,p的宣告周期也就結束,所以還會多呼叫解構式,
解決方案
如何避免這種臨時物件的產生呢?
只要把值傳遞的方式修改為參考傳遞的方式即可,這樣既不會呼叫拷貝建構式,也不會呼叫多一次臨時物件的解構式,減少額外不必要的開銷,
所以我們在函式形參設計時候,能夠用參考就用參考的方式,因為這樣可以減少物件的復制操作,減少而外的開銷,
代碼不驗證啦,因為比較簡單,可以自行驗證,修改 fun函式里形參為 Person& p;即可,
2. 型別轉換成臨時物件 / 隱式型別轉換保證函式呼叫成功
這種方式就是并且把型別轉化前的物件當作了形參傳遞給建構式,生成臨時物件臨時物件結束后就會呼叫解構式,
驗證臨時物件的而外開銷(2)
代碼依舊是上一個代碼,只是在main函式做了不一樣的動作
# include<iostream>
using namespace std;
class Person{
public:
Preson()
{
cout << "無參建構式!" << endl;
}
Person(int a)
{
m_age = a;
cout << "有參建構式!" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "拷貝建構式!" << endl;
}
~Person()
{
cout << "解構式!" << endl;
}
int fun(Person p) //普通的成員函式,注意引數是以值的方式呼叫的
{
p.m_age = 20; //這里修改對外界沒有印象
return p.m_age;
}
int m_age;
};
int main()
{
Person p;
p = 1000;
return 0;
}
首先預測一下該代碼執行的結果:
首先 呼叫一次無參建構式,一次解構式,
其次看看編譯器運行的結果:

為啥會多出一個有參建構式呢和解構式呢?
其實是由于 p = 1000;這句引起的,這里p的型別為 Person,而 1000為 int 型別,很明顯型別不一致,
編譯器其實偷偷的進行了型別轉換,如何轉換呢?看編譯器的呼叫都可以發現,其實就是創建一個臨時物件,這個臨時物件呼叫了有參建構式,并且把 這個1000作為形參,傳入有參建構式,當這個函式呼叫結束后,物件也就銷毀了,所以臨時物件會呼叫解構式,
解決方案
其實很簡單的:
只要把單引數建構式的復制(復制)陳述句,改為初始化陳述句就行,
那什么是復制陳述句和初始化陳述句呢?
兩者的區別就是
一個是創建物件同時賦值物件,也就是說創建時候就馬上初始化,這就是初始化;
一個是創建物件時候不賦值物件,而是等物件創建好,過后使用再賦值物件,這就是賦值陳述句啦;
那么我們只需要把:
Person p;
p = 1000;
修改為:
Person p = 1000;
這樣就不會有多一次的有參構造和析構的開銷了,
3. 函式回傳物件時候
在函式回傳物件時候,會創建一個臨時物件接收這個物件;從而呼叫了拷貝建構式,和解構式,
當你呼叫函式,沒有接識訓傳值時候,就會呼叫解構式,因為都沒有人接識訓傳值了,自然而然析構了,當你呼叫時候,有接識訓傳值時候,這個時候,并不會多呼叫一次解構式,而是直接把臨時物件回傳值,給了接受回傳值的變數來接收,
驗證臨時物件的而外開銷(3)
代碼:
# include<iostream>
using namespace std;
class Person{
public:
Preson()
{
cout << "無參建構式!" << endl;
}
Person(int a)
{
m_age = a;
cout << "有參建構式!" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "拷貝建構式!" << endl;
}
~Person()
{
cout << "解構式!" << endl;
}
int fun(Person p) //普通的成員函式,注意引數是以值的方式呼叫的
{
p.m_age = 20; //這里修改對外界沒有印象
return p.m_age;
}
int m_age;
};
Person test(Person & p)
{
Person p1; //這里會呼叫無參建構式和結束的一次解構式
p1.m_age = p.m_age;
return p1; //這里會多呼叫一次臨時拷貝和解構式
}
int main()
{
Person p;
test(p);
return 0;
}
看看執行結果:

其實很好理解:就是以值的方式回傳時候,就會多呼叫一次拷貝構造和解構式;
結果中的第一個析構時test函式里p1物件的析構,第二個析構時 回傳值時候臨時物件的析構;第三個析構時main函式里p物件的析構;
請注意我的test函式在呼叫時候,我并沒有給回傳值,此時;當我以回傳只接受時候,就會有不一樣結果:不一樣的地方就是,少了一次解構式,其實少的這次解構式時test函式里回傳值產生的臨時物件,因為,當你有物件接識訓傳值時候,就會直接把test函式里回傳值臨時物件給初始化接識訓傳值物件;
即,我修改main函式的代碼:
int main()
{
Person p;
Person p2 = test(p); //此時test回傳值臨時物件并不會析構,
//因為這里把臨時物件直接初始化了p2;
return 0;
}

可以說時編譯器優化手段吧,本來說 p2物件因該也是需要呼叫多一次拷貝建構式的,但是由于有臨時物件的初始化,所以p2物件就直接接管臨時物件了,所以上面結果最后的解構式,其實時p2物件的析構,并不是臨時物件的析構,
解決方案
其實也很簡單的解決辦法:有兩種:
-
當我們在接收函式回傳的物件時候,可以用右值參考接收,因為該函式回傳值是一個臨時變數,用一個右值參考接收它,使得它的生命周期得以延續,這樣就少呼叫一次解構式的開銷,(當然普通的物件接收也是可以)
-
當我們在設計函式里的return 陳述句中,不是回傳創建好的物件,而是回傳我們臨時創建的物件,即使用
retturn 型別別(形參); 這個時候,就可以直接避免return 物件;回傳時候又要呼叫多一次建構式,
這兩種行為就可以避免了建構式和解構式的產生,
但是,右值參考我還沒有寫到這文章,所以先不講右值參考的方案,講第二種方案:
也就是設計函式回傳陳述句 return時候,不要直接回傳物件,而是回傳臨時物件,這個臨時物件,
把這個代碼修改:
Person test(Person & p)
{
Person p1; //這里會呼叫無參建構式和結束的一次解構式
p1.m_age = p.m_age;
return p1; //這里會多呼叫一次臨時拷貝和解構式
}
修改為:
Person test(Person &p)
{
return Person(p.m_age);//直接回傳臨時物件,可以減少
}

其實,只要以值得形式回傳物件都會呼叫多一次拷貝建構式,所以我們盡量避免這種情況,用合適的方式解決它,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/298675.html
標籤:其他
上一篇:git branch分支管理思考
