C++中臨時(shí)對(duì)象的常見產(chǎn)生情況及其解決的方案
前言
在C++中很容易就寫出一些代碼,這些代碼的特點(diǎn)就是偷偷的給你產(chǎn)生了一些臨時(shí)對(duì)象,導(dǎo)致臨時(shí)對(duì)象會(huì)調(diào)用拷貝構(gòu)造函數(shù),賦值運(yùn)算符,析構(gòu)函數(shù),假如該對(duì)象還有繼承的話,也會(huì)調(diào)用父類的拷貝構(gòu)造函數(shù),賦值運(yùn)算賦函數(shù)等。這些臨時(shí)對(duì)象所調(diào)用的函數(shù),都是不必要的開銷,也就是說(shuō),我本意不想你給我調(diào)用這些函數(shù)的,但你編譯器卻給我偷偷的調(diào)用了,就是由于我程序員寫代碼產(chǎn)生臨時(shí)對(duì)象而產(chǎn)生的。
所以臨時(shí)對(duì)象產(chǎn)生的話題也應(yīng)運(yùn)而生,這篇文章主要是探討常見的臨時(shí)對(duì)象產(chǎn)生的情況,及其如何避免和解決這種臨時(shí)對(duì)象產(chǎn)生的方式。
1. 以值傳遞的方式給函數(shù)傳參
這種是最常見的產(chǎn)生嶺師對(duì)象的方式了。
以值傳遞的方式給函數(shù)傳參這種方式會(huì)直接調(diào)用對(duì)象的拷貝構(gòu)造函數(shù),生成一個(gè)臨時(shí)對(duì)象傳參給函數(shù)。當(dāng)臨時(shí)對(duì)象銷毀時(shí)候,也是函數(shù)形參銷毀,也是函數(shù)執(zhí)行完后,就會(huì)調(diào)用該臨時(shí)對(duì)象的析構(gòu)函數(shù)。此時(shí),無(wú)論是調(diào)用拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù),都是額外的開銷。
(驗(yàn)證是否調(diào)用拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù),可以在書寫拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù)驗(yàn)證)
(驗(yàn)證是否為臨時(shí)對(duì)象可以通過(guò)再函數(shù)內(nèi)部修改形參的值,在函數(shù)外部打印看看是否修改成功)
驗(yàn)證臨時(shí)對(duì)象的而外開銷(1)
# include<iostream>
using namespace std;
class Person{
public:
Preson()
{
cout << "無(wú)參構(gòu)造函數(shù)!" << endl;
}
Person(int a)
{
m_age = a;
cout << "有參構(gòu)造函數(shù)!" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "拷貝構(gòu)造函數(shù)!" << endl;
}
~Person()
{
cout << "析構(gòu)函數(shù)!" << endl;
}
int fun(Person p) //普通的成員函數(shù),注意參數(shù)是以值的方式調(diào)用的
{
p.m_age = 20; //這里修改對(duì)外界沒有印象
return p.m_age;
}
int m_age;
};
int main()
{
Person p(10);//初始化
p.fun(p);
return 0;
}
先來(lái)預(yù)測(cè)一下調(diào)用函數(shù)的次數(shù):也就是我們本意想調(diào)用的方式:
會(huì)執(zhí)行一次 Person的有參構(gòu)造函數(shù);
會(huì)執(zhí)行一次Person的析構(gòu)函數(shù);
于此同時(shí)我們看看,編譯結(jié)果實(shí)際情況:

和我們預(yù)期并不一樣?。?! 多了一次拷貝構(gòu)造函數(shù)和一次析構(gòu)函數(shù)。這兩個(gè)函數(shù)并不是我們希望要得,或者說(shuō),這個(gè)多余函數(shù)開銷是不必要的;
產(chǎn)生的原因也很好理解:
由于 fun成員函數(shù)里面的形參是Person p,這樣會(huì)導(dǎo)致在調(diào)用這個(gè)fun函數(shù)時(shí)候,會(huì)傳遞過(guò)去的是實(shí)參的復(fù)制品,臨時(shí)對(duì)象,并不是外面main函數(shù)的實(shí)參,這里可以在fun函數(shù)里修改一樣形參就可以發(fā)現(xiàn),外面的實(shí)參沒發(fā)生改變。
所以產(chǎn)生的臨時(shí)對(duì)象給形參傳參時(shí)候,在我們看來(lái)類似 Person p = p;實(shí)際上是Person p = temp;而這句 Person p = temp;就會(huì)發(fā)生拷貝構(gòu)造函數(shù)啦,于此同時(shí) fun函數(shù)調(diào)用結(jié)束后,p的聲明周期也就結(jié)束,所以還會(huì)多調(diào)用析構(gòu)函數(shù)。
解決方案
如何避免這種臨時(shí)對(duì)象的產(chǎn)生呢?
只要把值傳遞的方式修改為引用傳遞的方式即可。這樣既不會(huì)調(diào)用拷貝構(gòu)造函數(shù),也不會(huì)調(diào)用多一次臨時(shí)對(duì)象的析構(gòu)函數(shù)。減少額外不必要的開銷。
所以我們?cè)诤瘮?shù)形參設(shè)計(jì)時(shí)候,能夠用引用就用引用的方式,因?yàn)檫@樣可以減少對(duì)象的復(fù)制操作,減少而外的開銷。
代碼不驗(yàn)證啦,因?yàn)楸容^簡(jiǎn)單,可以自行驗(yàn)證,修改 fun函數(shù)里形參為 Person& p;即可。
2. 類型轉(zhuǎn)換成臨時(shí)對(duì)象 / 隱式類型轉(zhuǎn)換保證函數(shù)調(diào)用成功
這種方式就是并且把類型轉(zhuǎn)化前的對(duì)象當(dāng)作了形參傳遞給構(gòu)造函數(shù),生成臨時(shí)對(duì)象臨時(shí)對(duì)象結(jié)束后就會(huì)調(diào)用析構(gòu)函數(shù)。
驗(yàn)證臨時(shí)對(duì)象的而外開銷(2)
代碼依舊是上一個(gè)代碼,只是在main函數(shù)做了不一樣的動(dòng)作
# include<iostream>
using namespace std;
class Person{
public:
Preson()
{
cout << "無(wú)參構(gòu)造函數(shù)!" << endl;
}
Person(int a)
{
m_age = a;
cout << "有參構(gòu)造函數(shù)!" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "拷貝構(gòu)造函數(shù)!" << endl;
}
~Person()
{
cout << "析構(gòu)函數(shù)!" << endl;
}
int fun(Person p) //普通的成員函數(shù),注意參數(shù)是以值的方式調(diào)用的
{
p.m_age = 20; //這里修改對(duì)外界沒有印象
return p.m_age;
}
int m_age;
};
int main()
{
Person p;
p = 1000;
return 0;
}
首先預(yù)測(cè)一下該代碼執(zhí)行的結(jié)果:
首先 調(diào)用一次無(wú)參構(gòu)造函數(shù),一次析構(gòu)函數(shù)。
其次看看編譯器運(yùn)行的結(jié)果:

為啥會(huì)多出一個(gè)有參構(gòu)造函數(shù)呢和析構(gòu)函數(shù)呢?
其實(shí)是由于 p = 1000;這句引起的,這里p的類型為 Person,而 1000為 int 類型,很明顯類型不一致。
編譯器其實(shí)偷偷的進(jìn)行了類型轉(zhuǎn)換,如何轉(zhuǎn)換呢?看編譯器的調(diào)用都可以發(fā)現(xiàn),其實(shí)就是創(chuàng)建一個(gè)臨時(shí)對(duì)象,這個(gè)臨時(shí)對(duì)象調(diào)用了有參構(gòu)造函數(shù),并且把 這個(gè)1000作為形參,傳入有參構(gòu)造函數(shù),當(dāng)這個(gè)函數(shù)調(diào)用結(jié)束后,對(duì)象也就銷毀了,所以臨時(shí)對(duì)象會(huì)調(diào)用析構(gòu)函數(shù)。
解決方案
其實(shí)很簡(jiǎn)單的:
只要把單參數(shù)構(gòu)造函數(shù)的復(fù)制(復(fù)制)語(yǔ)句,改為初始化語(yǔ)句就行。
那什么是復(fù)制語(yǔ)句和初始化語(yǔ)句呢?
兩者的區(qū)別就是
一個(gè)是創(chuàng)建對(duì)象同時(shí)賦值對(duì)象,也就是說(shuō)創(chuàng)建時(shí)候就馬上初始化,這就是初始化;
一個(gè)是創(chuàng)建對(duì)象時(shí)候不賦值對(duì)象,而是等對(duì)象創(chuàng)建好,過(guò)后使用再賦值對(duì)象,這就是賦值語(yǔ)句啦;
那么我們只需要把:
Person p; p = 1000; 修改為: Person p = 1000;
這樣就不會(huì)有多一次的有參構(gòu)造和析構(gòu)的開銷了。
3. 函數(shù)返回對(duì)象時(shí)候
在函數(shù)返回對(duì)象時(shí)候,會(huì)創(chuàng)建一個(gè)臨時(shí)對(duì)象接收這個(gè)對(duì)象;從而調(diào)用了拷貝構(gòu)造函數(shù),和析構(gòu)函數(shù)。
當(dāng)你調(diào)用函數(shù),沒有接收返回值時(shí)候,就會(huì)調(diào)用析構(gòu)函數(shù),因?yàn)槎紱]有人接收返回值了,自然而然析構(gòu)了。當(dāng)你調(diào)用時(shí)候,有接收返回值時(shí)候,這個(gè)時(shí)候,并不會(huì)多調(diào)用一次析構(gòu)函數(shù),而是直接把臨時(shí)對(duì)象返回值,給了接受返回值的變量來(lái)接收。
驗(yàn)證臨時(shí)對(duì)象的而外開銷(3)
代碼:
# include<iostream>
using namespace std;
class Person{
public:
Preson()
{
cout << "無(wú)參構(gòu)造函數(shù)!" << endl;
}
Person(int a)
{
m_age = a;
cout << "有參構(gòu)造函數(shù)!" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "拷貝構(gòu)造函數(shù)!" << endl;
}
~Person()
{
cout << "析構(gòu)函數(shù)!" << endl;
}
int fun(Person p) //普通的成員函數(shù),注意參數(shù)是以值的方式調(diào)用的
{
p.m_age = 20; //這里修改對(duì)外界沒有印象
return p.m_age;
}
int m_age;
};
Person test(Person & p)
{
Person p1; //這里會(huì)調(diào)用無(wú)參構(gòu)造函數(shù)和結(jié)束的一次析構(gòu)函數(shù)
p1.m_age = p.m_age;
return p1; //這里會(huì)多調(diào)用一次臨時(shí)拷貝和析構(gòu)函數(shù)
}
int main()
{
Person p;
test(p);
return 0;
}
看看執(zhí)行結(jié)果:

其實(shí)很好理解:就是以值的方式返回時(shí)候,就會(huì)多調(diào)用一次拷貝構(gòu)造和析構(gòu)函數(shù);
結(jié)果中的第一個(gè)析構(gòu)時(shí)test函數(shù)里p1對(duì)象的析構(gòu),第二個(gè)析構(gòu)時(shí) 返回值時(shí)候臨時(shí)對(duì)象的析構(gòu);第三個(gè)析構(gòu)時(shí)main函數(shù)里p對(duì)象的析構(gòu);
請(qǐng)注意我的test函數(shù)在調(diào)用時(shí)候,我并沒有給返回值,此時(shí);當(dāng)我以返回只接受時(shí)候,就會(huì)有不一樣結(jié)果:不一樣的地方就是,少了一次析構(gòu)函數(shù),其實(shí)少的這次析構(gòu)函數(shù)時(shí)test函數(shù)里返回值產(chǎn)生的臨時(shí)對(duì)象,因?yàn)?,?dāng)你有對(duì)象接收返回值時(shí)候,就會(huì)直接把test函數(shù)里返回值臨時(shí)對(duì)象給初始化接收返回值對(duì)象;
即,我修改main函數(shù)的代碼:
int main()
{
Person p;
Person p2 = test(p); //此時(shí)test返回值臨時(shí)對(duì)象并不會(huì)析構(gòu),
//因?yàn)檫@里把臨時(shí)對(duì)象直接初始化了p2;
return 0;
}

可以說(shuō)時(shí)編譯器優(yōu)化手段吧。本來(lái)說(shuō) p2對(duì)象因該也是需要調(diào)用多一次拷貝構(gòu)造函數(shù)的,但是由于有臨時(shí)對(duì)象的初始化,所以p2對(duì)象就直接接管臨時(shí)對(duì)象了。所以上面結(jié)果最后的析構(gòu)函數(shù),其實(shí)時(shí)p2對(duì)象的析構(gòu),并不是臨時(shí)對(duì)象的析構(gòu)。
解決方案
其實(shí)也很簡(jiǎn)單的解決辦法:有兩種:
- 當(dāng)我們?cè)诮邮蘸瘮?shù)返回的對(duì)象時(shí)候,可以用右值引用接收,因?yàn)樵摵瘮?shù)返回值是一個(gè)臨時(shí)變量,用一個(gè)右值引用接收它,使得它的生命周期得以延續(xù),這樣就少調(diào)用一次析構(gòu)函數(shù)的開銷。(當(dāng)然普通的對(duì)象接收也是可以)
- 當(dāng)我們?cè)谠O(shè)計(jì)函數(shù)里的return 語(yǔ)句中,不是返回創(chuàng)建好的對(duì)象,而是返回我們臨時(shí)創(chuàng)建的對(duì)象,即使用
retturn 類類型(形參); 這個(gè)時(shí)候,就可以直接避免return 對(duì)象;返回時(shí)候又要調(diào)用多一次構(gòu)造函數(shù)。
這兩種行為就可以避免了構(gòu)造函數(shù)和析構(gòu)函數(shù)的產(chǎn)生。
但是,右值引用我還沒有寫到這文章,所以先不講右值引用的方案,講第二種方案:
也就是設(shè)計(jì)函數(shù)返回語(yǔ)句 return時(shí)候,不要直接返回對(duì)象,而是返回臨時(shí)對(duì)象,這個(gè)臨時(shí)對(duì)象。
把這個(gè)代碼修改:
Person test(Person & p)
{
Person p1; //這里會(huì)調(diào)用無(wú)參構(gòu)造函數(shù)和結(jié)束的一次析構(gòu)函數(shù)
p1.m_age = p.m_age;
return p1; //這里會(huì)多調(diào)用一次臨時(shí)拷貝和析構(gòu)函數(shù)
}
修改為:
Person test(Person &p)
{
return Person(p.m_age);//直接返回臨時(shí)對(duì)象,可以減少
}

其實(shí),只要以值得形式返回對(duì)象都會(huì)調(diào)用多一次拷貝構(gòu)造函數(shù),所以我們盡量避免這種情況,用合適的方式解決它。
到此這篇關(guān)于C++中臨時(shí)對(duì)象的常見產(chǎn)生情況及其解決的方案的文章就介紹到這了,更多相關(guān)C++ 臨時(shí)對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C++?functional庫(kù)中的仿函數(shù)使用方法
仿函數(shù)(functor)又稱之為函數(shù)對(duì)象(function?object),實(shí)際上就是?重載了()操作符?的?struct或class。本文將詳細(xì)講解C++中仿函數(shù)的使用方法,需要的可以參考一下2022-04-04
C++ OpenCV實(shí)戰(zhàn)之手勢(shì)識(shí)別
這篇文章主要介紹了如何利用C++?OpenCV實(shí)現(xiàn)手勢(shì)識(shí)別,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)OpenCV有一定幫助,感興趣的小伙伴可以了解一下2022-04-04
C語(yǔ)言深入探究自定義類型之結(jié)構(gòu)體與枚舉及聯(lián)合
今天我們來(lái)學(xué)習(xí)一下自定義類型,自定義類型包括結(jié)構(gòu)體、枚舉、聯(lián)合體,小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考2022-05-05
C++面試基礎(chǔ)之static關(guān)鍵字詳解
這篇文章主要給大家介紹了關(guān)于C++面試基礎(chǔ)之static關(guān)鍵字的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02
C語(yǔ)言實(shí)現(xiàn)訪問(wèn)及查詢MySQL數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)訪問(wèn)及查詢MySQL數(shù)據(jù)庫(kù)的方法,涉及C語(yǔ)言基于libmysql.lib實(shí)現(xiàn)訪問(wèn)MySQL數(shù)據(jù)庫(kù)的相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
MFC控件之CListCtrl的應(yīng)用實(shí)例教程
這篇文章主要介紹了MFC控件中CListCtrl的應(yīng)用方法,包括了針對(duì)表格的一些操作,是MFC中比較重要的一個(gè)控件類,需要的朋友可以參考下2014-08-08
C++學(xué)習(xí)之算術(shù)運(yùn)算符使用詳解
運(yùn)算符是計(jì)算機(jī)語(yǔ)言提供的能對(duì)數(shù)據(jù)進(jìn)行基本運(yùn)算操作的功能體。而算術(shù)運(yùn)算符用來(lái)對(duì)數(shù)字型數(shù)據(jù)進(jìn)行數(shù)學(xué)語(yǔ)義上的加、減、乘、除。本文通過(guò)講解清楚算術(shù)運(yùn)算符,讓大家了解使用C++運(yùn)算符時(shí)應(yīng)該注意的事項(xiàng)2022-06-06

