寫出高效代碼的12條建議
大家好,我是程序喵,最近迷上了云宗主,所以放了個(gè)云韻的封面,下次估計(jì)會是美杜莎!

今天和大家介紹一下能讓C++代碼更加高效的幾個(gè)小技巧,話不多說,以下為本文目錄:
參數(shù)傳遞方式:值傳遞還是引用傳遞
函數(shù)返回方式:按值返回還是按引用返回
使用移動語義
避免創(chuàng)建臨時(shí)對象
了解返回值優(yōu)化
考慮預(yù)分配內(nèi)存
考慮內(nèi)聯(lián)
迭代 vs 遞歸
選擇高效的算法
利用緩存
profiling
other碎碎念
以下為正文:
值傳遞還是引用傳遞:
一般情況下使用const的引用參數(shù)。對于函數(shù)本身會拷貝的參數(shù),最好使用值傳遞,但只有當(dāng)參數(shù)的類型支持移動語義時(shí)才這樣。
在某些情況下,值傳遞并移動實(shí)際上是向函數(shù)傳遞參數(shù)的最佳方式(注意看后面的tips),例如:
class A {public:A(const std::string &str) { str_ = str; }private:std::string str_;};
可以考慮改為這種形式:
class A {public:A(std::string str) { str_ = std::move(str); }private:std::string str_;};
因?yàn)闊o論如何都會對它們進(jìn)行拷貝。
tips:看有些資料說后者值傳遞是更好的參數(shù)傳遞方式,貌似有些道理,但是我沒找到非常合理的理由,有知道的讀者可以在評論區(qū)留言。
按值返回還是按引用返回:
可以通過從函數(shù)中按引用方式返回對象,以避免對象發(fā)生不必要的復(fù)制。但有時(shí)不可能通過引用返回對象,例如編寫重載的operator+和其他類似運(yùn)算符時(shí)。
永遠(yuǎn)都不要返回指向局部對象的引用或指針,局部對象會在函數(shù)退出時(shí)被銷毀。
但是,按值返回對象通常沒啥大問題。因?yàn)橐话闱闆r下他們會觸發(fā)返回值優(yōu)化或移動語義,即不會有多余的拷貝動作。
使用移動語義:
盡量確保對象擁有移動構(gòu)造函數(shù)和移動賦值運(yùn)算符。對象有了移動語義后,許多操作都會更加高效,特別是與標(biāo)準(zhǔn)庫和算法相結(jié)合時(shí)。
避免創(chuàng)建臨時(shí)對象:
沒有必要的臨時(shí)對象能避免就避免。一般來說,應(yīng)該避免迫使編譯器構(gòu)造臨時(shí)對象的情況。盡管有時(shí)這是不可避免的,但是至少應(yīng)該意識到這項(xiàng)“特性”的存在,這樣才不會為實(shí)際性能和分析結(jié)果而感到驚訝。編譯器還會使用移動語義使臨時(shí)對象的效率更高。這是要在類中添加移動語義的另一個(gè)原因。
《More Effective C++》第19條款中介紹過:所謂的臨時(shí)對象并不是程序員創(chuàng)建的用于存儲臨時(shí)值的對象,而是指編譯器層面上的臨時(shí)對象:這種臨時(shí)對象不是由程序員創(chuàng)建,而是由編譯器為了實(shí)現(xiàn)某些功能(例如函數(shù)返回,類型轉(zhuǎn)換等)而創(chuàng)建。
比如下面的代碼就會有臨時(shí)對象的產(chǎn)生:
void Func(const std::string& s);char arr[]="hello";Func(aar); // here
返回值優(yōu)化
通過值返回對象的函數(shù)可能導(dǎo)致創(chuàng)建一個(gè)臨時(shí)對象。看下面的代碼:
Person createPerson(){Person newP { "Marc", "Gregoire", 42 };return newP;}
假如像這樣調(diào)用這個(gè)函數(shù)(假設(shè)Person 類已經(jīng)實(shí)現(xiàn)了operator<<運(yùn)算符):
cout << createPerson();即便這個(gè)調(diào)用沒有將createPerson()的結(jié)果保存在任何地方,也必須將結(jié)果保存在某個(gè)地方,才能傳遞給operator<<。為此編譯器創(chuàng)建一個(gè)臨時(shí)變量,來保存createPerson()返回的Person 對象。
即使這個(gè)函數(shù)的結(jié)果沒有在任何地方使用,編譯器也仍然可能會生成創(chuàng)建臨時(shí)對象的代碼:
createPerson();編譯器可能生成代碼來創(chuàng)建一個(gè)臨時(shí)對象來保存返回值,即使這個(gè)返回值沒有使用也是如此。
不過吧,編譯器會在大多數(shù)情況下優(yōu)化掉臨時(shí)變量,以避免復(fù)制和移動。
關(guān)于返回值優(yōu)化我之前有篇文章介紹過,感興趣的可以看看這個(gè):《左值引用、右值引用、移動語義、完美轉(zhuǎn)發(fā),你知道的不知道的都在這里》
預(yù)分配內(nèi)存:
比如標(biāo)準(zhǔn)化容器中的reserve,需要頻繁創(chuàng)建內(nèi)存的地方可以考慮預(yù)分配一塊內(nèi)存出來,避免頻繁的創(chuàng)建內(nèi)存。
內(nèi)聯(lián)函數(shù):
短函數(shù)可以使用內(nèi)聯(lián)消除函數(shù)開銷。
迭代 vs 遞歸:
這里我更傾向于選擇迭代方式,而不是遞歸。遞歸占用大量棧內(nèi)存,且可能會產(chǎn)生很多不必要的臨時(shí)對象構(gòu)建。
選擇效率更高的算法:
學(xué)計(jì)算機(jī)的估計(jì)沒有不知道算法的吧,學(xué)算法估計(jì)沒有人不知道如何計(jì)算時(shí)間復(fù)雜度和空間復(fù)雜度吧,在平時(shí)開發(fā)過程中遇到算法問題時(shí)我們可盡量選擇效率更高的算法,比如O(N)和O(N2)的算法,我們肯定要選擇O(N)的呀,這里可以了解下C++的
盡可能多的使用緩存:
將某些數(shù)據(jù)保存下來供下次使用,避免再次獲取或重新計(jì)算它們。如果任務(wù)或計(jì)算特別慢,應(yīng)該保證不執(zhí)行那些沒有必要的任務(wù)或者重復(fù)計(jì)算。
網(wǎng)絡(luò)通信:如果頻繁發(fā)起相同的網(wǎng)絡(luò)請求,可考慮將第一次的網(wǎng)絡(luò)請求結(jié)果保存在內(nèi)存中,或文件中?
磁盤訪問:如果頻繁訪問一個(gè)文件,可考慮將這個(gè)文件的內(nèi)容保存在內(nèi)存中。
數(shù)學(xué)計(jì)算:某些很耗時(shí)很復(fù)雜的運(yùn)算,可考慮只執(zhí)行這種計(jì)算一次,然后共享結(jié)果。
對象分配:如果需要大量頻繁創(chuàng)建和銷毀短期對象,可考慮使用對象池。
線程創(chuàng)建:如果需要大量頻繁創(chuàng)建和銷毀線程,可考慮使用線程池。
做客戶端開發(fā)的朋友應(yīng)該都聽說多級緩存的概念,就是這個(gè)原理。
profiling:
性能問題永遠(yuǎn)離不開profiling工具,多用profiling工具。工具篇我之前介紹過:《這么多性能調(diào)優(yōu)工具,看看你知道幾個(gè)?》
other碎碎念:
選擇合適的數(shù)據(jù)結(jié)構(gòu):
選擇合適的STL,想清楚什么時(shí)候用棧,什么時(shí)候用隊(duì)列,什么時(shí)候用數(shù)組,什么時(shí)候用鏈表。
某些if-else可改為switch,效率可能更高(知道為什么嗎,不知道的可以留言,人多的話考慮輸出一篇文章)。
優(yōu)先考慮棧內(nèi)存,而不是堆內(nèi)存(免得頻繁的申請釋放內(nèi)存)。
如何函數(shù)不需要返回值,就不要設(shè)置返回值。
使用位操作,移位代替乘法除法操作。
構(gòu)造函數(shù)時(shí)使用初始化方式,而不是賦值。
A::A() : a_(a) {} // betterA::A() {a_ = a;}
明確使用模板帶來的益處:
如果使用模板并沒有給你的開發(fā)帶來任何益處,是不是可以考慮不使用它,因?yàn)檎{(diào)試起來真的麻煩。
函數(shù)參數(shù)的個(gè)數(shù)不要太多。
擅用emplace,有些情況下會省去一次構(gòu)造的開銷。
最后想說一句:

