5 範圍、模板和C++其他特性
5.1 範圍
原則5.1 使用名字空間進行歸類,避免符號衝突
說明:名字空間主要解決符號衝突問題。
樣本:兩個不同項目的全域範圍都有一個類 Foo, 這樣在編譯或運行時造成衝突。如果每個項目將
代碼置於不同名字空間中
namespace project1
{
class Foo;
//…
}
namespace project2
{
class Foo;
//…
}
作為不同符號自然不會衝突:project1::Foo 和 project2::Foo。
建議名字空間的名稱採用全小寫,為避免衝突,可採用項目(產品組件)或目錄結構。
用名字空間把整個源檔案封裝起來, 以區別於其它名字空間,但是這些內容除外:檔案包含(include),
全域標識的聲明/定義以及類的前置聲明。
樣本:
#include "a.h"
DEFINE_bool(someflag, false, “dummy flag”);
class C; //全域名字空間中類C的前置聲明
namespace a{class A;} //a::A 的前置聲明
//以上代碼就不要放在名字空間b中。
namespace b
{
//b中的代碼…
} //namespace b
規則5.1 不要在標頭檔中或者#include之前使用using指示符
說明:使用using會影響後續代碼,易造成符號衝突,所以不要在標頭檔以及源檔案中的#include之前
使用using。
樣本:
//標頭檔a.h
namespace A
{
int f(int);
}
//標頭檔b.h
namespace B
{
int f(int);
}
using namespace B;
void g()
{
f(1);
}
//原始碼a.cpp
#include “a.h”
using namespace A;
#include “b.h”
void main()
{
g();//using namespace A在#include “b.h”之前,引發歧意:A::f,B::f調用不明確
}
建議5.1 盡量少使用嵌套類
說明:一個類在另一個類中定義,這樣的類被稱為嵌套類。嵌套類是其外圍類的成員,嵌套類也被稱
為成員類。
class Foo
{
private:
//Bar是嵌套在Foo中的成員類
class Bar
{
//…
};
};
跟其它類一樣,嵌套類可以聲明為public、priate和protected屬性,因此,從外部存取嵌套類,遵循
類成員的訪問規則。
一般來說,應該盡量少用嵌套類。嵌套類最適合用來對它們的外層類實現細節建模(如:方便實現鏈表、
容器等演算法),且在這個類需要訪問外圍類所有成員(包括私人成員)時才使用嵌套類。 不要通過嵌套
類來進行命名分組,應該使用名字空間。
建議5.2 儘可能不使用局部類
說明:定義在函數體內的類稱為局部類。局部類只在定義它的局部域內可見。
局部類的成員函數必須被定義在類定義中,且不能超過15行代碼。否則,代碼將變得很難理解。
建議5.3 使用靜態成員函數或名字空間內的非成員函數,避免使用全域函數
說明:非成員函數放在名字空間內可避免汙染全域範圍。
如果你必須定義非成員函數, 又只是在 .cpp 檔案中使用它, 也可使用static關鍵字 (如 static int
Foo() {...}) 限定其範圍。
建議5.4 避免class類型的全域變數,盡量用單件模式
說明:靜態生存周期的對象, 包括全域變數, 靜態變數, 靜態類成員變數, 以及函數靜態變數, 都必
須是原生資料類型 (POD : Plain Old Data)。
靜態變數的建構函式, 解構函式以及初始化操作的調用順序在C++標準中未明確定義,從而導致難以發
現的 bug。比如範圍結束時,某個靜態變數已經被析構了,但其他代碼還試圖訪問該變數,導致系
統崩潰。所以只允許 POD 類型的靜態變數。同樣,不允許用函數傳回值來初始化靜態變數。
5.2 模板
模板可以衍生出一系列的類和函數,這是一種形式的代碼複用。但要注意,這種形式的複用是原始碼級
而不是目標代碼級的。也就是說,對模板的每一次執行個體化都會產生一份新的原始碼。與此相反,繼承允
許複用基類的大部分目標代碼。
濫用模板會造成三個後果:第一,代碼規模的過度膨脹;第二,當修改模板時,很難預料是否會對原先
正常工作的代碼造成不良影響;第三,很難確保模板所有可能的合法執行個體化都能正常工作。所以模板的
使用要仔細且有節制。
建議5.5 謹慎使用模板,只使用模板的基礎特性
說明:模板對編譯器的要求很高,當前對模板100%支援的編譯器幾乎沒有,因此,使用前應作測試,
特別是涉及偏特化,模板參數等進階特性時。
模板的錯誤提示比較難懂,產生的錯誤要映射回模板後才能顯示給編程者看,但映射畢竟不是錯代碼
本身,所以有時很難看懂,有時甚至失真,對於人員素質要求也高,增加了維護難度。
建議5.6 注意使用模板的代碼膨脹
說明:模板會為每個類型產生一個執行個體,如果使用不當,會產生過多執行個體,特別是根據常量執行個體化時。
建議5.7 模板類型應該使用引用或指標
說明:執行個體化和參數傳遞複雜類型(結構體,對象),傳值的代價很高;引用和指標可以提高效率。
建議5.8 模板如果有約束條件,請在模板定義處顯式說明
說明:編譯對模板的檢查較弱,很難保證檢察所有錯誤,因此進行顯式說明可以減少模板使用錯誤。
建議5.9 兩個模組之間介面中盡量不要暴露模板
說明:因為在編譯器最佳化選項開啟的情況下,其編譯的符號為不確定,導致依賴。
5.3 其他
規則5.2 不要在extern "C"內部使用#include包含其他標頭檔
說明:在C++代碼中調用C的庫檔案,需要用extern "C"來告訴編譯器:這是一個用C寫成的庫檔案,請
用C的方式來連結它們。
嚴格的講,只應該把函數、變數以及函數類型這三種對象放置於extern "C"的內部。
如果把#include指令放在extern "C"裡面,可能會導致extern "C"嵌套而帶來如下幾個風險:
extern "C"嵌套過深,導致編譯錯誤
extern "C"嵌套後,改變其他某些檔案的編譯連結方式
建議5.10 避免使用友元
說明:友元擴大了 (但沒有打破) 類的封裝邊界。友元會導致類間強耦合,打破封裝,暴露出具體實
現,從而使友元和類的實現緊耦合;友元不可繼承,降低可繼承性。
例外:某些情況下, 相對於將類成員聲明為public, 使用友元是更好的選擇, 尤其是你只允許另一個
類訪問該類的私人成員時。
建議5.11 避免使用RTTI
說明:RTTI允許在運行時識別對象的類型。使用RTTI很容易違反“開放封閉原則”。如果要根據派生
類的類型來確定執行不同邏輯代碼, 虛函數無疑更合適,在對象內部就可以處理類型識別問題;如果
要在對象外部的代碼中判斷類型, 考慮使用雙重指派方案, 如訪問者模式,在對象本身之外確定類的
類型。所以,不建議使用RTTI,除非在一些特定場合如某些單元測試中會用到。
建議5.12 使用sizeof(變數)而不是sizeof(類型)
說明:使用 sizeof(varname),當代碼中變數類型改變時會自動更新。
struct DATA data;
memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(DATA)); //當data改為其他類型時,出錯。
另外,對數組來說,sizeof(陣列變數) 並不一定是元素的個數,元素個數是sizeof(數組變
量)/sizeof(陣列變數[0])。