01??
C++語言編程規范-常量
02??
初始化和類型轉換
聲明、定義與初始化
03??
禁止用 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 函數初始化類對象。
04??
變量使用時才聲明并初始化
說明:變量在使用前未賦初值,是常見的低級編程錯誤。使用前才聲明變量并同時初始化,非常方便 地避免了此類低級錯誤。在函數開始位置聲明所有變量,后面才使用變量,作用域覆蓋整個函數實現,容易導致如下問題:
? 程序難以理解和維護:變量的定義與使用分離。
? 變量難以合理初始化:在函數開始時,經常沒有足夠的信息進行變量初始化,往往用某個默認的空值(比如零)來初始化,這通常是一種浪費,如果變量在被賦于有效值以前使用,還會導致錯誤。遵循變量作用域最小化原則與就近聲明原則,使得代碼更容易閱讀,方便了解變量的類型和初始值。特別是,應使用初始化的方式替代聲明再賦值。
示例:
//不好的例子:聲明與初始化分離
string?name;?//聲明時未初始化:調用缺省構造函數
//…….?
name=”zhangsan”;?//再次調用賦值操作符函數;聲明與定義在不同的地方,理解相對困難
//好的例子:聲明與初始化一體,理解相對容易
string?name(”zhangsan”);?//調用一次構造函數
05??
避免構造函數做復雜的初始化,可以使用“init”函數
說明:正如函數的變量都在函數內部初始化一樣,類數據成員最好的初始化場所就是構造函數,數據成員都應該盡量在構造函數中初始化。
以下情況可以使用?init()函數來初始化:
? 需要提供初始化返回信息。
? 數據成員初始化可能拋異常。
? 數據成員初始化失敗會造成該類對象初始化失敗,引起不確定狀態。
??數據成員初始化依賴 this 指針:構造函數沒結束,對象就沒有構造出來,構造函數內不能使用 this 成員;
? 數據成員初始化需要調用虛函數。在構造函數和析構函數中調用虛函數,會導致未定義的行為。
示例:數據成員初始化可能拋異常:
class?CPPRule?
{
public:?
? CPPRule():size_(0), res (null) {};?//僅進行值初始化
??long?init(int?size)?
? {?
? ??//根據傳入的參數初始化size_, 分配資源res?
? }?
private:?
??int?size_;?
? ResourcePtr* res;?
};?
//使用方法:
CPPRule a;?
a.init(100);
06??
初始化列表要嚴格按照成員聲明順序來初始化它們
說明:編譯器會按照數據成員在類定義中聲明的順序進行初始化,而不是按照初始化列表中的順序,
如果打亂初始化列表的順序實際上不起作用,但會造成閱讀和理解上的混淆;特別是成員變量之間存在依賴關系時可能導致 BUG。
示例:
//不好的例子:初始化順序與聲明順序不一致
class?Employee?
{?
public:?
Employee(const?char* firstName,?const?char* lastName)?
: firstName_(firstName), lastName_(lastName)?
, email_(firstName_ +?"."?+ lastName_ +?"@huawei.com") {};?
private:?
string?email_, firstName_, lastName_;?
};
類定義 email_是在 firstName_,lastName_之前聲明,它將首先初始化,但使用了未初始化的 firstName_和lastName_,導致錯誤。在成員聲明時,應按照成員相互依賴關系按順序聲明。
07??
類型轉換
避免使用類型分支來定制行為:類型分支來定制行為容易出錯,是企圖用 C++編寫 C 代碼的明顯標志。
這是一種很不靈活的技術,要添加新類型時,如果忘記修改所有分支,編譯器也不會告知。使用模板和虛函數,讓類型自己而不是調用它們的代碼來決定行為。
08??
使用 C++風格的類型轉換,不要使用 C 風格的類型轉換
說明:C++的類型轉換由于采用關鍵字,更醒目,更容易查找,編程中強迫程序員多停留思考片刻,謹慎使用強制轉換。
C++使用?const_cast, dynamic_cast, static_cast, reinterpret_cast?等新的類型轉換,它們允許用戶選擇適當級別的轉換符,而不是像 C 那樣全用一個轉換符。
dynamic_cast:主要用于下行轉換,dynamic_cast 具有類型檢查的功能。dynamic_cast 有一定的開銷,建議在調測代碼中使用。
#include?<iostream>
#include<typeinfo>
class?Base?{
public:
? ??virtual?~Base() {}?// 需要虛函數才能啟用RTTI
};
class?Derived?:?public?Base {
public:
? ??void?derived_func()?{
? ? ? ? std::cout <<?"Derived function called"?<< std::endl;
? ? }
};
int?main()?{
? ? Base* base_ptr =?new?Derived();
? ??// 使用 dynamic_cast 進行安全的下行轉換
? ? Derived* derived_ptr =?dynamic_cast<Derived*>(base_ptr);
? ??if?(derived_ptr) {
? ? ? ? std::cout <<?"轉換成功"?<< std::endl;
? ? ? ? derived_ptr->derived_func();?// 安全調用派生類函數
? ? }?else?{
? ? ? ? std::cout <<?"轉換失敗"?<< std::endl;
? ? }
? ??// 嘗試轉換不匹配的類型
? ? Derived* another_ptr =?dynamic_cast<Derived*>(new?Base());
? ??if?(another_ptr) {
? ? ? ? std::cout <<?"轉換成功"?<< std::endl;
? ? }?else?{
? ? ? ? std::cout <<?"轉換失敗"?<< std::endl;
? ? }
? ??delete?base_ptr;
? ??delete?another_ptr;
? ??return?0;
}
對于引用類型的 dynamic_cast:
#include<iostream>
#include?<typeinfo>
class?Base?{
public:
? ??virtual?~Base() {}
};
class?Derived?:?public?Base {
public:
? ??void?derived_func()?{
? ? ? ? std::cout <<?"Derived function called"?<< std::endl;
? ? }
};
void?process(Base& base)?{
? ??try?{
? ? ? ? Derived& derived =?dynamic_cast<Derived&>(base);
? ? ? ? std::cout <<?"轉換成功"?<< std::endl;
? ? ? ? derived.derived_func();
? ? }?catch?(const?std::bad_cast& e) {
? ? ? ? std::cout <<?"轉換失敗: "?<< e.what() << std::endl;
? ? }
}
int?main()?{
? ? Derived derived;
? ? Base base;
? ? std::cout <<?"處理 Derived 對象:"?<< std::endl;
? ??process(derived);?// 轉換成功
? ? std::cout <<?"\n處理 Base 對象:"?<< std::endl;
? ??process(base); ? ?// 轉換失敗
? ??return?0;
}
static_cast:和 C 風格轉換相似可做值的強制轉換,或上行轉換(把派生類的指針或引用轉換成基類的指針或引用)。該轉換經常用于消除多重繼承帶來的類型歧義,是相對安全的。下行轉換(把基類的
指針或引用轉換成派生類的指針或引用)時,由于沒有動態類型檢查,所以不安全的,不提倡下行轉換。
上行轉換(派生類到基類)- 這是 static_cast 最安全的用法之一
class?Base?{
public:
? ??virtual?~Base() {}
? ??virtual?void?base_func()?{ std::cout <<?"Base function"?<< std::endl; }
};
class?Derived?:?public?Base {
public:
? ??void?derived_func()?{ std::cout <<?"Derived function"?<< std::endl; }
};
int?main()?{
? ? Derived derived;
? ? Base* base_ptr =?static_cast<Base*>(&derived);?// 上行轉換,安全
? ? base_ptr->base_func();?// 調用基類函數
? ??return?0;
}
reinterpret_cast:用于轉換不相關的類型。reinterpret_cast 強制編譯器將某個類型對象的內存新解釋成另一種類型,相關代碼可移植不好。建議對 reinterpret_cast<>的用法進行注釋,有助于減少維
護者在看到這種轉換時的顧慮。
良好的編碼實踐:添加注釋
// 注意:這里使用 reinterpret_cast 是因為我們需要將特定內存區域
// 解釋為另一種類型,這是平臺相關的操作,不具有可移植性
class?MemoryMappedDevice?{
public:
? ??MemoryMappedDevice(void* base_addr)?
? ? ? ? :?control(reinterpret_cast<volatile?uint32_t*>(base_addr)),
? ? ? ? ??data(reinterpret_cast<volatile?uint32_t*>(static_cast<uintptr_t>(base_addr) +?4)) {}
private:
? ??volatile?uint32_t* control;
? ??volatile?uint32_t* data;
};
const_cast:用于移除對象的 const 屬性,使對象變得可修改。
int?main()?{
? ??const?int?ci =?10;
? ??const?int* ci_ptr = &ci;
? ??// 移除 const 屬性
? ??int* i_ptr =?const_cast<int*>(ci_ptr);
? ? *i_ptr =?20;?// 現在可以修改值
? ? std::cout <<?"ci: "?<< ci << std::endl;?// 輸出可能是 20,也可能還是 10
? ? std::cout <<?"*i_ptr: "?<< *i_ptr << std::endl;?// 輸出 20
? ??return?0;
}
09??
extern?void?Fun(DerivedClass* pd);?
void?Gun(BaseClass* pb)?
{?
//不好的例子: C風格強制轉換,轉換會導致對象布局不一致,編譯不報錯,運行時可能會崩潰
DerivedClass* pd = (DerivedClass *)pb;?
//好的例子: C++風格強制轉換,明確知道pb實際指向DerivedClass?
DerivedClass* pd = dynamic_cast< DerivedClass *>(pb);?
if(pd)?
Fun(pd);
}