準則
1.少使用define
- define所定義的常量會在預處理的時候被替代,出錯編譯器不容易找到錯誤。而且還沒有作用范圍限制,推薦使用const
- define宏定義的函數,容易出錯,而且參數需要加上小括號,推薦使用inline
- 有的類中例如數組初始化需要添加元素個數,如果define定義的常量沒有作用范圍限制,推薦使用enums
2.確定對象使用前先初始化
- 為內置型對象進行手動初始化
- 構造函數最好使用成員初始化列,如果在構造函數中進行賦值的話相當于先初始化默認值,然后有賦給值,導致浪費時間。
- 為了免除跨編譯單元之初始化次序,將非本地靜態變量替換成本地靜態變量。因為靜態變量在程序編譯時就賦值存在,不會導致引用時未構造。
3.為多態基類聲明析構函數
- 帶多態性質的基類應該聲明一個virtual的析構函數。如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數
- Classs的設計目的如果不是作為base classes使用,或不是為了具備多態性,就不該聲明virtual析構函數,因為設置virtual會使派生類帶上virtual 函數表,導致浪費空間。
class A{virtual ~A(){}
}
class B:pulic A{}
A *b=new (B);
//當未定義virtual 基類析構函數時,會調用A的析構函數,可能導致未釋放B新增內成員的的空間
delete b;
4.不要讓異常逃離析構函數
class DBconn{
public:
void close(){
db.close();
closed=true;
}
~DBconn(){
if(!closed)
try{
db.close()
}catch(...){
std:abort();//結束程序,不要讓異常傳出去,造成不確定后果
}
}
}
private:
DBConnection db;
bool closed;
};
//在對元素析構時,當兩個及以上元素出現異常時,程序就會停止或者造成不明確的行為,造成內存呢泄露
-
析構函數絕對不要吐出異常。如果一個析構函數調用的函數可能出現異常,析構函數應該捕捉任何異常,然后吞下他們或者結束程序。
-
如果客戶需要對某個操作在運行期間拋出的異常做出反應,class 應該提供一個普通函數執行該操作。
5.不要在構造函數或者析構函數里面調用virtual函數
對于virtual 一般是多態定義的,但是當構造函數構造子類使會先構造父類,當在構造器中調用virtual會導致調用的是父類版本的virtual,因為在構造父類時,此時編譯器還不知道子類有什么成員,所以用當前版本的。
6.在operator中處理自我賦值
//當rhs與this是同一個對象時,delete pb會導致低下rhs的pb也delete 導致報錯 Widget::operator=(const Widget& rhs){delete pb;pb=new Bitmap(*rhs.pb);return *this; } //進行驗同測試,當時new出現異常時,會導致this.pb被釋放 Widget::operator=(const Widget& rhs){if(&rhs==this)return *this;delete pb;pb=new Bitmap(*rhs.pb);return *this; } //這樣既不怕是統一對象,也不怕new出錯 Widget::operator=(const Widget& rhs){Bitmap *pOrig=pb;pb=new Bitmap(*rhs.pb);delete pOrig;return *this; }
7.以獨立的語句將newed對象置于智能指針
processWidget(std:trl::shared_ptrM<Widget> pr(new Widget),priority())
對于c++執行這句話,以什么樣的次序進行執行彈性很大,與java與c#不同1。如果以 new widget,priority(),shared_ptrM(),順序則可能出現內存泄露的風險。當priority出錯()時,將無法將new出來的內存進行刪除,因此最好以單獨語句執行。
std:trl::shared_ptrM<Widget> pr(new Widget);
processWidget(std:trl::shared_ptrM<Widget> pr,priority())
8.以引用傳遞代替值傳遞
- 按值傳遞可能會使特化的信息別切割
class window{
public:virtual :display();
}class myWindow:public window{private:int size;public:virtual:display();
}
void useDisPlay1(window w){w.display();
}
void useDisplay2(window& w){w.display();
}
mywidow w;
useDisPlay1(w);//按值傳遞會使 mywindow所得有特化的信息被切割
userIdsPlay2(w);//按引用傳遞則不會使切割
- 按值傳遞會讓編譯器去構造副本,對于一般自定義的類來說浪費時間和空間
- 按值傳遞適合內置類型,STL迭代器和函數對象。因為傳遞引用的實質使傳遞指針。
9.inline的使用
- 將大多數inline限制在小型、被頻繁調用的函數身上,這可使以后的調試過程和二進制升級更容易,也可使潛在的代碼膨脹問題最小化,使程序提升速度最大化。
- 不要只因為function template 出現在頭文件就將他們設置為inline。
inline一定被放在頭文件是因為編譯器為了將函數調用代碼替換為函數本體 要知道函數本體長什么樣子
template 一定放在頭文件里是 因為一旦被使用,編譯器會將它具體化,得知道它長什么樣子。
10.將文件間的編譯依存關系降到最低
- 如果使用object reference 或者obejct point 可以實現就不要用 object了。
- 如果能夠,盡量以class聲明替代函數。
- 為聲明式和定義式提供不同的頭文件
- 或者將聲明類定義為abstrate 類,實現類繼承進行繼承。
就是將類的實現和申明寫成兩個類,然后在聲明類中引用實現類的指針。這樣當實現類中的成員進行變化時,聲明類不用重新編譯。而且聲明類中也無法看出方法的具體實現。
11.避免遮蔽繼承而來的名稱
class base{
private:
int x;
public :
void fun()
}class drived :public base{public:using base:fun()void fun();
}
//子類fun()會遮蔽父類fun(),想用父類fun就要用 using base:fun()
derived的作用域
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ICDjcePv-1692172341730)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220830185337010.png)]
drived classes內的名稱會遮蔽base class內的名稱,為了讓遮蔽的名稱重用,用using 或者轉交函數
12.private繼承
- private繼承意味is-implemented-in-terms of(根據某物實現)。它通常比復合成員的級別低。當derived class 需要訪問protected base class的成員,或需要重新定義繼承而來的virtual函數時,這么設計是合理的。
- 和復合不同,private繼承可以造成empty base 最優化。這對致力于”對象尺寸最小化“的程序開發者而言,可能很重要。
13.多重繼承
-
多重繼承比單一繼承復雜。它可能導致新的歧義性,以及對virtual繼承的需要。
-
virtual繼承會導致速度大小,初始化等等成本。如果virtual base classes 不帶任何數據,將是最具有實用價值的情況。
-
多重繼承的確有正當途徑,當其中一個情節涉及”public繼承某個Interface class“ 和private 繼承某個協助實現的class的兩相組合。例如public 繼承的接口在private 繼承的類中有方法去實現。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NEncUj9i-1692172341731)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220830215346169.png)]
14.將與參數無關的代碼抽離template
-
templates生成多個classes和多個函數,所以任何template代碼都不該與某個造成膨脹的template參數產生依賴關系。
-
因非類型模板參數而造成的代碼膨脹,往往可以消除,做法是以函數參數或者class成員替換template函數。類如去定義類中一些參數,這樣的參數可以寫在類中。
-
因類型參數造成的代碼膨脹,往往可以降低。做法是讓帶有完全相同的二進制表述的具體表述共享實現代碼。類如int與long可能公用一個模板。