規則2.1 禁止用memcpy、memset初始化非pod物件
說明:pod 全稱是「plain old data」,是c++ 98標準(iso/iec 14882, first edition, 1998-09-01)中引入的乙個概念, pod型別主要包括int, char, float,double,enumeration,void,指標等原始型別及其集合型別,不能使用封裝和面對物件特性(如使用者定義的構造/賦值/析構函式、基類、虛函式等)。
由於非pod型別比如非集合型別的class物件,可能存在虛函式,記憶體布局不確定,跟編譯器有關,濫用記憶體拷貝可能會導致嚴重的問題。
即使對集合型別的class,使用直接的記憶體拷貝和比較,破壞了資訊隱蔽和資料保護的作用,也不提倡memcpy、memset操作。
示例:×××產品程式異常退出(core dump)。
經過現場環境的模似,程式產生coredump,其原因是:在初始化函式內使用memset(this, 0, sizeof(*this))進行了類的初始化,將類的虛函式表指標被清空,從而導致使用空指標。
解決方案:使用c++建構函式初始化,不要使用memset函式初始化類物件。
建議2.1 變數使用時才宣告並初始化
說明:變數在使用前未賦初值,是常見的低階程式設計錯誤。使用前才宣告變數並同時初始化,非常方便地避免了此類低階錯誤。
在函式開始位置宣告所有變數,後面才使用變數,作用域覆蓋整個函式實現,容易導致如下問題: l
程式難以理解和維護:變數的定義與使用分離。 l
變數難以合理初始化:在函式開始時,經常沒有足夠的資訊進行變數初始化,往往用某個預設的空值(比如零)來初始化,這通常是一種浪費,如果變數在被賦於有效值以前使用,還會導致錯誤。
遵循變數作用域最小化原則與就近宣告原則, 使得**更容易閱讀,方便了解變數的型別和初始值。特別是,應使用初始化的方式替代宣告再賦值。
示例:
//不好的例子:宣告與初始化分離
string name; //宣告時未初始化:呼叫預設建構函式
//…….
name=」zhangsan」; //
再次呼叫賦值操作符函式;宣告與定義在不同的地方,理解相對困難
//好的例子:宣告與初始化一體,理解相對容易
string name(」zhangsan」); //
呼叫一次建構函式
建議2.2 避免建構函式做複雜的初始化,可以使用「init」函式
說明:正如函式的變數都在函式內部初始化一樣,類資料成員最好的初始化場所就是建構函式,資料成員都應該盡量在建構函式中初始化。
以下情況可以使用init()函式來初始化: l
需要提供初始化返回資訊。 l
資料成員初始化可能拋異常。 l
資料成員初始化失敗會造成該類物件初始化失敗,引起不確定狀態。 l
資料成員初始化依賴this指標:建構函式沒結束,物件就沒有構造出來,建構函式內不能使用this成員; l
資料成員初始化需要呼叫虛函式。在建構函式和析構函式中呼叫虛函式,會導致未定義的行為。
示例:資料成員初始化可能拋異常:
class
cpprule
; //
僅進行值初始化
long
init(
intsize)
private:
intsize_;
resourceptr* res;};
//使用方法:
cpprule a;
a.init(100);
建議2.3 初始化列表要嚴格按照成員宣告順序來初始化它們
說明:編譯器會按照資料成員在類定義中宣告的順序進行初始化,而不是按照初始化列表中的順序,如果打亂初始化列表的順序實際上不起作用,但會造成閱讀和理解上的混淆;特別是成員變數之間存在依賴關係時可能導致bug。
示例: //
不好的例子:初始化順序與宣告順序不一致
class
employee ;
private:
string email_, firstname_, lastname_;};
類定義email_是在firstname_, lastname_之前宣告,它將首先初始化,但使用了未初始化的firstname_和lastname_,導致錯誤。
在成員宣告時,應按照成員相互依賴關係按順序宣告。
建議2.4 明確有外部依賴關係的全域性與靜態物件的初始化順序
說明:如果全域性物件a的成員變數有外部依賴,比如依賴另外乙個全域性變數b,在a的建構函式中訪問b,隱含的規則就是b先於a初始化,然而全域性與靜態物件的初始化與析構順序未有嚴格定義,無法確保b已經完成初始化,而每次生成可執行程式都可能發生變化,這類bug難以定位。
通常採用單件(singleton)模式或者把有依賴關係的全域性物件放在乙個檔案中定義來明確初始化順序。同乙個檔案中,若全域性物件a在全域性物件b之前定義,則a一定會在b之前初始化;但是不同檔案中的全域性物件就沒有固定的初始化順序。可以在main()或 pthread_once() 內初始化乙個執行期間不**的指標。
避免使用型別分支來定製行為:型別分支來定製行為容易出錯,是企圖用c++編寫c**的明顯標誌。這是一種很不靈活的技術,要新增新型別時,如果忘記修改所有分支,編譯器也不會告知。使用模板和虛函式,讓型別自己而不是呼叫它們的**來決定行為。
規則2.2 使用c++風格的型別轉換,不要使用c風格的型別轉換
說明:c++的型別轉換由於採用關鍵字,更醒目,更容易查詢,程式設計中強迫程式設計師多停留思考片刻,謹慎使用強制轉換。
c++使用const_cast, dynamic_cast, static_cast, reinterpret_cast等新的型別轉換,它們允許使用者選擇適當級別的轉換符,而不是像c那樣全用乙個轉換符。
dynamic_cast:主要用於下行轉換,dynamic_cast具有型別檢查的功能。dynamic_cast有一定的開銷,建議在調測**中使用。
static_cast:和c風格轉換相似可做值的強制轉換,或上行轉換(把派生類的指標或引用轉換成基類的指標或引用)。該轉換經常用於消除多重繼承帶來的型別歧義,是相對安全的。下行轉換(把基類的指標或引用轉換成派生類的指標或引用)時,由於沒有動態型別檢查,所以不安全的,不提倡下行轉換。
reinterpret_cast:用於轉換不相關的型別。reinterpret_cast強制編譯器將某個型別物件的記憶體重新解釋成另一種型別,相關**可移植不好。建議對reinterpret_cast<> 的用法進行注釋,有助於減少維護者在看到這種轉換時的顧慮。
const_cast:用於移除物件的 const屬性,使物件變得可修改。
示例:extern
void
fun(derivedclass* pd);
void
gun(baseclass* pb)
建議2.5 避免使用reinterpret_cast
說明:reinterpret_cast用於轉換不相關型別。嘗試用reinterpret_cast將一種型別強制轉換另一種型別,這破壞了型別的安全性與可靠性,是一種不安全的轉換。不同型別之間盡量避免轉換。
建議2.6 避免使用const_cast
說明:const_cast用於移除物件的 const性質。
const屬性提供一種安全感,讓程式設計師知道這個定義是固定不變的,從而不需要擔心後面的變化。如果const屬性在程式設計師不知道的地方被消除,會帶來很多嚴重的後果。
示例:不好的例子
unsigned
const int
arraysize = 1024;
int&newarraysize =
const_cast
<
int&>(arraysize);
newarraysize = 2048;
這裡如果不通過引用或者指標訪問arraysize,那麼arraysize的值始終是1024。可是如果被作為乙個指標或者引用傳給其他函式進行取值的話,會發現值變成了2048。
示例:不好的例子:強制去掉入參的const屬性,導致函式可以對入參進行修改。
void
setobj(tbase
const
*obj)
建議2.7 使用虛函式替換dynamic_cast
說明:很多剛從c語言轉過了的程式設計師習慣這樣的思路:若物件的型別是t1,則做某種處理;若物件的型別是t2,則做另外的處理等等。但c++提供了更好的解決方案:虛函式。
虛函式與dynamic_cast型別轉換相比: l
虛函式更安全,不會出現強制轉換錯的情況; l
虛函式效率更高:用函式指標,避免條件判斷; l
虛函式不需要在編碼時確定物件的真實型別,而dynamic_cast必須告知要轉成的型別,執行時若型別不當返回空指標或者拋異常; l
虛函式適用性更強:虛函式是真正動態繫結;型別轉換當增加或刪除乙個派生類時,dynamic_cast必須增減相應的**。
C 隱式和顯式 初始化,型別轉換
1.隱式和顯式初始化 1.1 c 隱式初始化 int ival 1024 string hello hello world.1.2 c 顯式初始化 int ival 1024 string hello hello world.ps 注意這裡 語法是拷貝建構函式而不是賦值運算 因為乙個新物件被定義一定...
型別初始化
最近兩天在看 net 本質論 其中提到了一型別初始化中比較關鍵的兩個方法 型別初始化器 cctor 和建構函式 ctor 建構函式不用多說,上學時就知道,但是型別初始化器我是剛剛知道,它是乙個無返回值 無引數 靜態的函式,在型別首次被載入時呼叫,接下來來看個例子 class class1 ctor ...
C 直接初始化和複製初始化
在c 裡,物件初始化是乙個非常重要但又容易令人混淆的問題。這裡是自己的一些總結。一。初始化與賦值的含義 初始化 包括建立 或說定義 物件並且賦給初值。如果乙個物件只被建立而沒有被初始化,則該變數只能用於被賦值 賦值 擦除物件的當前值並用新值代替。二。內建型別 一 直接初始化 1.空初始化 即無引數無...