減少C++程式碼編譯時間的方法
c++ 的程式碼包含標頭檔案和實現檔案兩部分, 標頭檔案一般是提供給別人(也叫客戶)使用的, 但是一旦標頭檔案發生改變,不管多小的變化,所有引用他的檔案就必須重新編譯,編譯就要花時間,假如你做的工程比較大(比如二次封裝chrome這類的開發),重新編譯一次的時間就會浪費上班的大部分時間,這樣幹了一天挺累的, 但是你的老闆說你沒有產出,結果你被fired, 是不是很怨啊, 如果你早點看到這段文章,你就會比你的同事開發效率高那麼一些,那樣被fired就不會是你了,你說這篇文章是不是價值千金!開個玩笑 :)
言歸正傳,怎樣介紹編譯時間呢, 我知道的就3個辦法:
- 刪除不必要的#include,替代辦法 使用前向宣告 (forward declared )
- 刪除不必要的一大堆私有成員變數,轉而使用 ”impl” 方法
- 刪除不必要的類之間的繼承
為了講清楚這3點,還是舉個例項比較好,這個例項我會一步一步的改進(因為我也是一點一點摸索出來了,如果哪裡說錯了, 你就放心的噴吧,我會和你在爭論到底的,呵呵)
現在先假設你找到一個新工作,接手以前某個程式設計師寫的類,如下
// old.h: 這就是你接收的類 // #include <iostream> #include <ostream> #include <list> // 5 個 分別是file , db, cx, deduce or error , 水平有限沒有模板類 // 只用 file and cx 有虛擬函式. #include "file.h" // class file #include "db.h" // class db #include "cx.h" // class cx #include "deduce.h" // class deduce #include "error.h" // class error class old : public file, private db { public: old( const cx& ); db get_db( int, char* ); cx get_cx( int, cx ); cx& fun1( db ); error fun2( error ); virtual std::ostream& print( std::ostream& ) const; private: std::list<cx> cx_list_; deduce deduce_d_; }; inline std::ostream& operator<<( std::ostream& os,const old& old_val ) { return old_val.print(os); }
這個類看完了, 如果你已經看出了問題出在哪裡, 接下來的不用看了, 你是高手, 這些基本知識對你來說太小兒科,要是像面試時被問住了愣了一下,請接著看吧
先看怎麼使用第一條: 刪除不必要的#include
這個類引用 5個標頭檔案, 那意味著那5個標頭檔案所引用的標頭檔案也都被引用了進來, 實際上, 不需要引用5 個,只要引用2個就完全可以了
1.刪除不必要的#include,替代辦法 使用前向宣告 (forward declared )
1.1刪除標頭檔案 iostream, 我剛開始學習c++ 時照著《c++ primer》 抄,只要看見關於輸入,輸出就把 iostream 標頭檔案加上, 幾年過去了, 現在我知道不是這樣的, 這裡只是定義輸出函式, 只要引用ostream 就夠了
1.2.ostream標頭檔案也不要, 替換為 iosfwd , 為什麼, 原因就是, 引數和返回型別只要前向宣告就可以編譯通過, 在iosfwd 檔案裡 678行(我的環境是vs2013,不同的編譯環境具體位置可能會不相同,但是都有這句宣告) 有這麼一句
typedef basic_ostream<char, char_traits<char> > ostream;
inline std::ostream& operator<<( std::ostream& os,const old& old_val )
{ return old_val.print(os); }
除此之外,要是你說這個函式要操作ostream 物件, 那還是需要#include <ostream> , 你只說對了一半, 的確, 這個函式要操作ostream 物件, 但是請看他的函式實現,
裡面沒有定義一個類似 std::ostream os, 這樣的語句,話說回來,但凡出現這樣的定義語句, 就必須#include 相應的標頭檔案了 ,因為這是請求編譯器分配空間,而如果只前向宣告 class XXX; 編譯器怎麼知道分配多大的空間給這個物件!
看到這裡, old.h標頭檔案可以更新如下了:
// old.h: 這就是你接收的類 // #include <iosfwd> //新替換的標頭檔案 #include <list> // 5 個 分別是file , db, cx, deduce or error , 水平有限沒有模板類 // 只用 file and cx 有虛擬函式. #include "file.h" // class file , 作為基類不能刪除,刪除了編譯器就不知道例項化old 物件時分配多大的空間了 #include "db.h" // class db, 作為基類不能刪除,同上 #include "cx.h" // class cx #include "deduce.h" // class deduce // error 只被用做引數和返回值型別, 用前向宣告替換#include "error.h" class error; class old : public file, private db { public: old( const cx& ); db get_db( int, char* ); cx get_cx( int, cx ); cx& fun1( db ); error fun2( error ); virtual std::ostream& print( std::ostream& ) const; private: std::list<cx> cx_list_; // cx 是模版型別,既不是函式引數型別也不是函式返回值型別,所以cx.h 標頭檔案不能刪除 deduce deduce_d_; // deduce 是型別定義,也不刪除他的標頭檔案 }; inline std::ostream& operator<<( std::ostream& os,const old& old_val ) { return old_val.print(os); }
到目前為止, 刪除了一些程式碼, 是不是心情很爽,據說看一個程式設計師的水平有多高, 不是看他寫了多少程式碼,而是看他少寫了多少程式碼。
如果你對C++ 程式設計有更深一步的興趣, 接下來的文字你還是會看的,再進一步刪除程式碼, 但是這次要另闢蹊徑了
2. 刪除不必要的一大堆私有成員變數,轉而使用 ”impl” 方法
2.1.使用 ”impl” 實現方式寫程式碼,減少客戶端程式碼的編譯依賴
impl 方法簡單點說就是把 類的私有成員變數全部放進一個impl 類, 然後把這個類的私有成員變數只保留一個impl* 指標,程式碼如下
// file old.h class old { //公有和保護成員 // public and protected members private: //私有成員, 只要任意一個的標頭檔案發生變化或成員個數增加,減少,所有引用old.h的客戶端必須重新編譯 // private members; whenever these change, // all client code must be recompiled };
改寫成這樣:
// file old.h class old { //公有和保護成員 // public and protected members private: class oldImpl* pimpl_; // 替換原來的所有私有成員變數為這個impl指標,指標只需要前向宣告就可以編譯通過,這種寫法將前向宣告和定義指標放在了一起, 完全可以。 //當然,也可以分開寫 // a pointer to a forward-declared class }; // file old.cpp struct oldImpl { //真正的成員變數隱藏在這裡, 隨意變化, 客戶端的程式碼都不需要重新編譯 // private members; fully hidden, can be // changed at will without recompiling clients };
不知道你看明白了沒有, 看不明白請隨便寫個類試驗下,我就是這麼做的,當然凡事也都有優缺點,下面簡單對比下:
使用impl 實現類 | 不使用impl實現類 | |
優點 | 型別定義與客戶端隔離, 減少#include 的次數,提高編譯速度,庫端的類隨意修改,客戶端不需要重新編譯 | 直接,簡單明瞭,不需要考慮堆分配,釋放,記憶體洩漏問題 |
缺點 | 對於impl的指標必須使用堆分配,堆釋放,時間長了會產生記憶體碎片,最終影響程式執行速度, 每次呼叫一個成員函式都要經過impl->xxx()的一次轉發 | 庫端任意標頭檔案發生變化,客戶端都必須重新編譯 |
改為impl實現後是這樣的:
// 只用 file and cx 有虛擬函式. #include "file.h" #include "db.h" class cx; class error; class old : public file, private db { public: old( const cx& ); db get_db( int, char* ); cx get_cx( int, cx ); cx& fun1( db ); error fun2( error ); virtual std::ostream& print( std::ostream& ) const; private: class oldimpl* pimpl; //此處前向宣告和定義 }; inline std::ostream& operator<<( std::ostream& os,const old& old_val ) { return old_val.print(os); } //implementation file old.cpp class oldimpl{ std::list<cx> cx_list_; deduce dudece_d_; };
3. 刪除不必要的類之間的繼承
物件導向提供了繼承這種機制,但是繼承不要濫用, old class 的繼承就屬於濫用之一, class old 繼承file 和 db 類, 繼承file是公有繼承,繼承db 是私有繼承
,繼承file 可以理解, 因為file 中有虛擬函式, old 要重新定義它, 但是根據我們的假設, 只有file 和 cx 有虛擬函式,私有繼承db 怎麼解釋?! 那麼唯一可能的理由就是:
通過 私有繼承—讓某個類不能當作基類去派生其他類,類似Java裡final關鍵字的功能,但是從例項看,顯然沒有這個用意, 所以這個私有繼承完全不必要, 應該改用包含的方式去使用db類提供的功能, 這樣就可以
把”db.h”標頭檔案刪除, 把db 的例項也可以放進impl類中,最終得到的類是這樣的:
// 只用 file and cx 有虛擬函式. #include "file.h" class cx; class error; class db; class old : public file { public: old( const cx& ); db get_db( int, char* ); cx get_cx( int, cx ); cx& fun1( db ); error fun2( error ); virtual std::ostream& print( std::ostream& ) const; private: class oldimpl* pimpl; //此處前向宣告和定義 }; inline std::ostream& operator<<( std::ostream& os,const old& old_val ) { return old_val.print(os); } //implementation file old.cpp class oldimpl{ std::list<cx> cx_list_; deduce dudece_d_; };
小結一下:
這篇文章只是簡單的介紹了減少編譯時間的幾個辦法:
1. 刪除不必要的#include,替代辦法 使用前向宣告 (forward declared )
2. 刪除不必要的一大堆私有成員變數,轉而使用 ”impl” 方法
3. 刪除不必要的類之間的繼承
這幾條希望對您有所幫助, 如果我哪裡講的不夠清楚也可以參考附件,哪裡有完整的例項,也歡迎您發表評論, 大家一起討論進步,哦不,加薪。 呵呵,在下篇文章我將把impl實現方式再詳細分析下,期待吧…
相關文章
- tcp減少2msl的時間TCP
- 利用源生成器,在編譯階段生成對映程式碼,減少執行時反射編譯反射
- 編寫良好的程式碼:如何減少程式碼的認知負荷
- 如何減少 Hyperf 框架的掃描時間框架
- 減少熱備方法遷移資料庫的停機時間資料庫
- 使用MVVM減少控制器程式碼實戰(減少56%)MVVM
- 多些時間能少寫些程式碼
- J2EE開發中減少編寫程式碼工作量的幾種方法
- Qt 獲取程式編譯時間QT編譯
- 優化C++程式碼(2):C++程式碼的編譯過程優化C++編譯
- 恆訊科技科普:減少伺服器響應時間的10種方法伺服器
- 如何加快C++程式碼的編譯速度C++編譯
- 給VC編出的程式減肥,減少你程式的尺寸 (轉)
- 程式中減少使用 if 語句的方法集錦
- 程式中減少使用if語句的方法集錦
- [譯] 優化 Swift 的編譯時間優化Swift編譯
- 編譯檢查dsp程式碼的方法編譯
- Vue 減少和服務端互動的樣板程式碼一種方法Vue服務端
- Webpack + Vue,部署時減少包體積的幾種方法WebVue
- 減少程式碼中該死的 if else 巢狀巢狀
- 減少SQL Server死鎖的方法SQLServer
- Java 8 中的方法引用,輕鬆減少程式碼量,提升可讀性!Java
- 編譯C++ 程式的過程編譯C++
- [譯] 減少 Python 中迴圈的使用Python
- 論減少程式碼中return語句的騷操作
- 使用BatchNorm替代LayerNorm可以減少Vision Transformer訓練時間和推理時間BATORM
- C/C++開發者必不可少的15款編譯器+IDEC++編譯IDE
- MySQL實現當前資料表的所有時間都增加或減少指定的時間間隔(推薦)MySql
- 移動spa商城優化記(二)--- 減少70%的打包等待時間優化
- 【譯】如何使用webpack減少vuejs打包的大小WebVueJS
- 經過4次優化我把python程式碼耗時減少95%優化Python
- 我為什麼減少使用C++ (轉)C++
- [翻譯]關於Swift的編譯時間優化Swift編譯優化
- 寫程式碼時,編譯器比你聰明編譯
- MySQL 減少InnoDB系統表空間的大小MySql
- 前端頁面優化,減少 reflow 的方法前端優化
- IIS減少工作執行緒阻塞的方法執行緒
- 減少SQL日誌的三種方法(轉)SQL