C++設計開發規範(3):型別設計規範
阿新 • • 發佈:2019-02-05
“一切以簡單為美“
C++中的型別繁多,其中包括(具體)類、基類、介面、結構、列舉、陣列等。在此規範中,我們不詳細討論抽象類和介面,因為這兩個型別屬於一個特殊的邏輯分組,和擴充套件性有關,我們在擴充套件性設計規範中進行討論。
任何的程式語言都可以看成是一個型別系統。在這個型別系統中,每個型別都扮演這各自的職責,各有其意義:
n (具體)類:在遵循某個特定的開發封閉原則的前提下的對行為和屬性的封裝。使用class宣告。
n 基類:在遵循某個特定的開發封閉原則的前提下的對行為和屬性的適度封裝。使用class宣告。
n 介面:抽象了某個/某些特定的行為,為物件提供了一種抽象的分類準則,它應該是基本穩定的。使用interface宣告。
n 結構:用於定義小而簡單的型別。使用struct宣告。
n 列舉:用於定義一小組值,這一小組值代表著一個邏輯分類。如一星期中的每天。
在型別系統中不同的型別適用於不同的用途,因此遵循著不同的規則,代表著不同的意義。
l 推薦使用基類,如果:
a.實現某個特定的介面(可選)
b.對於子類,它的確具有抽象意義
c.它通過與子類共享成員(屬性、函式)或/和定義虛擬函式來實現抽象
d.它具有/不具有狀態
l 推薦使用抽象類,如果:
e.具有基類的所有特徵
f.不能被例項化(至少具有一個純虛擬函式)
l 推薦使用介面,如果:
a.它代表了一類行為的抽象,這樣的一類行為可以作為一種分類規則區別其他不同類的物件
b. 它的定義應該是基本穩定的,否則考慮使用抽象類而不是介面
c.它定義功能而不是實現功能,不具有任何狀態
d.精心定義的介面只做一件事情
l 推薦使用結構,如果:
a.它在邏輯上代表一個獨立的值,與基本型別(int、double等)相似
b.它不需要虛擬函式(包括虛解構函式)
c.它總是在棧中被例項化,例項比較小而且生命週期比較段或經常被內嵌在其他物件中
l 推薦使用靜態類,如果你設計的類具有下面的特徵:
a.它定義的所有的函式都是public static的
b.它不需要被例項化
c.它往往被用於定義一些幫助函式或基於簡單性的考慮來整合某個特定的功能。
√ 要求區分介面、結構和類:
結構
使用struct關鍵字宣告,用於定義以資料為中心的實體型別,如
struct PageSetting。
介面
使用interface(即struct)關鍵字宣告,用於定義抽象的行為集合的實體型別,
如interface IDrawing。
類
使用class關鍵字宣告,用於定義以行為為中心的實體型別,如
class TokenParser。
× 不要使用private和protected繼承,除非萬不得已。
例如,
//不好的做法:使用private繼承來實現組合
class A{}
class B : private A{}
//好的做法:使用private成員來實現組合
class A{}
class B
{
private:
A m_a;
}
× 不要使用virutal繼承,除非萬不得已。
例如,
//不好
class Base1{}
class Base21 : virtual public Base1{}
class Base22 : virtual public Base1{}
class Base3 : public Base21,public Base22{}
l 推薦不要過度使用多繼承,特別是實現繼承。
√ 要求用小類代替巨類
小類更易於編寫,更易於保證正確、測試和使用。而大類承擔太多職責,削弱了封裝性。
l 推薦用組合代替繼承
避免繼承帶來的重負:繼承是C++中第二緊密地耦合關係,僅次於友元關係。軟體
工程的原則之一就是減少耦合。在適當的時候,應該使用組合代替繼承。
例外:
如果需要改寫基類的虛擬函式;
如果需要訪問基類的保護成員;
如果需要控制多型;
如果需要在基類之前構造已使用過的物件,或在基類之後銷燬此物件。
× 不要公開內部資料
資料隱藏是強大的抽象方式,也是強大的模組化機制。應該避免將內部資料控制代碼/ 指標暴露給外部。
例如,
class Component
{
public:
char* GetBuffer() {return m_buffer;}//不好
const char* GetBuffer()const{return m_buffer;}//好
private:
char* m_buffer;
}
l 推薦不要在抽象類中定義任何資料成員。
√ 要求在抽象類中定義protect而非public/private的建構函式。
例如,
class AddinBase
{
public:
virtual void Authorizing() = 0;
protected:
AddinBase(){}
}
l 推薦使用介面來定義抽象類的行為。
例如,
class IComponent
{
virtual void AddControl() = 0;
virtual void PendingModification() = 0;
}
class Component : public IComponent
{
public:
virtual void AddControl(){…}
virtual void PendingModification() = 0;
}
√ 要求一個介面只做一件事情。
× 不要定義介面,如果這個介面定義的功能很不穩定。
× 不要在介面中定義冗餘的、存在二義性的pure virtual函式。
例如,
interface ILayout
{
//返回頁數,頁索引從0開始
virtual int GetActivePage () const = 0;
//返回傳遞給UI引數的頁數,頁索引從1開始,返回值= GetActivePage()+1
virtual int GetUI ActivePage () const = 0; //冗餘
}
× 不要使用C風格的定義方式。
例如,
//不好
typedef struct tagColorSwatch
{
…
} ColorSwatch;
//好
struct ColorSwatch
{
…
};
√ 要求為結構提供一個預設的建構函式。
√ 要求為拷貝建構函式設為私有/保護成員,如果不需要拷貝建構函式。
例如,
struct ExportConfiguration
{
protected:
ExportConfiguration(ExportConfiguration& other){}
};
√ 要求為結構提供拷貝建構函式,如果:預設的拷貝建構函式的行為不是所需要的。
√ 要求為結構過載operator=,如果:預設的operator=行為不是所需要的。
√ 要求為結構過載operator==,operator!=,如果:預設的operator==,operator!=行為不是所需要的。
例如,
struct TextProperties
{
const wchar_t* FontName;
bool Bold;
bool Italic;
bool operator==(TextProperties& other)
{
return (Bold == other.Bold) &&
(Italic == other.Italic) &&
(strcmp(FontName, other. FontName) == 0) ) ;
}
};
× 不要設計面面俱到、非常靈活的結構。
例如,
//不好
PageCombineMergeSetting
{
const wchar_t* SourceFilename;
int SourcePage;
bool IsMerge;
bool IsCombine;
Graph::CdRect TargetRectangle; //當IsMerge==true時有效
Graph::CdRect TargetPage; //當IsCombine==true時有效
}
//好,改寫為兩個結構
struct MergeSetting
{
const wchar_t* SourceFilename;
int SourcePage;
Graph::CdRect TargetRectangle;
}
struct CombineSetting
{
const wchar_t* SourceFilename;
int SourcePage;
Graph::CdRect TargetPage;
}
l 推薦結構中元素的個數應適中。若結構中元素個數過多可考慮依據某種原則把元素組成不同的子結構,以減少原結構中元素的個數。
l 推薦仔細設計結構中元素的佈局與排列順序,使結構容易理解、節省佔用空間,並減少引起誤用現象。
l 推薦使用__declspec(align(x))方式定義結構的位元組對齊方式:
例如,
//定義一個8位元組對齊的結構
__declspec(align(8))
struct A{
double a,
int b;
}
× 不要使用C風格的定義方式。
例如,
//不好
typedef enum tagCOLORSWATCHTYPE
{
…
} COLORSWATCHTYPE;
//好
enum kColorSwatchType
{
…
};
√ 要求優先使用列舉而不要使用靜態常量或巨集定義。
例如,
//不好
struct ApplicationInfo
{
static const int UnknownProduct = 0;
static const int BusinessProduct = 1;
static const int NewsProduct = 2;
…
};
#define FreeProduct 3
//好
enum kProductType
{
kProductType_Unknown,
kProductType_Business,
kProductType_News,
kProductType_Free
}
× 不要把列舉用於開放的集合。
例如,作業系統的版本,朋友的名字等。
× 不要把sentinel值包含在列舉值中。
例如,
//好
enum kDeskType
{
kDeskType_Unknown = 0,
kDeskType_Circular = 1,
kProductType_Rectangular = 2,
kProductType_LastValue = 2//不好,不需要定義這個列舉值
}
√ 要求為簡單列舉型別提供零值。
例如,
enum kCompressionType
{
kCompressionType_None
kCompressionType_GZip,
kCompressionType_Deflate
}
enum kRequestType
{
kRequestType_Error,
kRequestType_Warning,
kRequestType_Information
}
√ 要求使用複數名詞/名詞短語來命名標記列舉。
例如,
enum kFileShareModes
{
kFileShareModes_Read = 1,
kFileShareModes_Write = 2,
kFileShareModes_ReadWrite = kFileShareModes_Read| kFileShareModes_Write,
}