程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Google C++編程風格指南(三):C++類

Google C++編程風格指南(三):C++類

編輯:C++入門知識

關於類的注意事項,總結一下:1. 不在構造函數中做太多邏輯相關的初始化; 2. 編譯器提供的默認構造函數不會對變量進行初始化,如果定義了其他構造函數,編譯器不再提供,需要編碼者自行提供默認構造函數;3. 為避免隱式轉換,需將單參數構造函數聲明為explicit;……

類是C++中基本的代碼單元,自然被廣泛使用。本節列舉了在寫一個類時要做什麼、不要做什麼。
1. 構造函數(Constructor)的職責
構造函數中只進行那些沒有實際意義的(trivial,譯者注:簡單初始化對於程序執行沒有實際的邏輯意義,因為成員變量的“有意義”的值大多不在構造函數中確定)初始化,可能的話,使用Init()方法集中初始化為有意義的(non-trivial)數據。
定義:在構造函數中執行初始化操作。
優點:排版方便,無需擔心類是否初始化。
缺點:在構造函數中執行操作引起的問題有:
1) 構造函數中不易報告錯誤,不能使用異常。
2) 操作失敗會造成對象初始化失敗,引起不確定狀態。
3) 構造函數內調用虛函數,調用不會派發到子類實現中,即使當前沒有子類化實現,將來仍是隱患。
4) 如果有人創建該類型的全局變量(雖然違背了上節提到的規則),構造函數將在main()之前被調用,有可能破壞構造函數中暗含的假設條件。例如,gflags尚未初始化。
結論:如果對象需要有意義的(non-trivial)初始化,考慮使用另外的Init()方法並(或)增加一個成員標記用於指示對象是否已經初始化成功。
2. 默認構造函數(Default Constructors)
如果一個類定義了若干成員變量又沒有其他構造函數,需要定義一個默認構造函數,否則編譯器將自動生產默認構造函數。
定義:新建一個沒有參數的對象時,默認構造函數被調用,當調用new[](為數組)時,默認構造函數總是被調用。
優點:默認將結構體初始化為“不可能的”值,使調試更加容易。
缺點:對代碼編寫者來說,這是多余的工作。
結論:
如果類中定義了成員變量,沒有提供其他構造函數,你需要定義一個默認構造函數(沒有參數)。默認構造函數更適合於初始化對象,使對象內部狀態(internal state)一致、有效。
提供默認構造函數的原因是:如果你沒有提供其他構造函數,又沒有定義默認構造函數,編譯器將為你自動生成一個,編譯器生成的構造函數並不會對對象進行初始化。
如果你定義的類繼承現有類,而你又沒有增加新的成員變量,則不需要為新類定義默認構造函數。

3. 明確的構造函數(Explicit Constructors)
對單參數構造函數使用C++關鍵字explicit。
定義:通常,只有一個參數的構造函數可被用於轉換(conversion,譯者注:主要指隱式轉換,下文可見),例如,定義了Foo::Foo(string name),當向需要傳入一個Foo對象的函數傳入一個字符串時,構造函數Foo::Foo(string name)被調用並將該字符串轉換為一個Foo臨時對象傳給調用函數。看上去很方便,但如果你並不希望如此通過轉換生成一個新對象的話,麻煩也隨之而來。為避免構造函數被調用造成隱式轉換,可以將其聲明為explicit。
優點:避免不合時宜的變換。
缺點:無。
結論:
所有單參數構造函數必須是明確的。在類定義中,將關鍵字explicit加到單參數構造函數前:explicit Foo(string name);
例外:在少數情況下,拷貝構造函數可以不聲明為explicit;特意作為其他類的透明包裝器的類。類似例外情況應在注釋中明確說明。
4. 拷貝構造函數(Copy Constructors)
僅在代碼中需要拷貝一個類對象的時候使用拷貝構造函數;不需要拷貝時應使用DISALLOW_COPY_AND_ASSIGN。
定義:通過拷貝新建對象時可使用拷貝構造函數(特別是對象的傳值時)。
優點:拷貝構造函數使得拷貝對象更加容易,STL容器要求所有內容可拷貝、可賦值。
缺點:C++中對象的隱式拷貝是導致很多性能問題和bugs的根源。拷貝構造函數降低了代碼可讀性,相比按引用傳遞,跟蹤按值傳遞的對象更加困難,對象修改的地方變得難以捉摸。
結論:
大量的類並不需要可拷貝,也不需要一個拷貝構造函數或賦值操作(assignment operator)。不幸的是,如果你不主動聲明它們,編譯器會為你自動生成,而且是public的。
可以考慮在類的private中添加空的(dummy)拷貝構造函數和賦值操作,只有聲明,沒有定義。由於這些空程序聲明為private,當其他代碼試圖使用它們的時候,編譯器將報錯。為了方便,可以使用宏DISALLOW_COPY_AND_ASSIGN:
// 禁止使用拷貝構造函數和賦值操作的宏
// 應在類的private:中使用
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
TypeName(const TypeName&);
void operator=(const TypeName&)

class Foo {
public:
Foo(int f);
~Foo();

private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};

如上所述,絕大多數情況下都應使用DISALLOW_COPY_AND_ASSIGN,如果類確實需要可拷貝,應在該類的頭文件中說明原由,並適當定義拷貝構造函數和賦值操作,注意在operator=中檢測自賦值(self-assignment)情況。
在將類作為STL容器值得時候,你可能有使類可拷貝的沖動。類似情況下,真正該做的是使用指針指向STL容器中的對象,可以考慮使用std::tr1::shared_ptr。

5. 結構體和類(Structs vs. Classes)
僅當只有數據時使用struct,其它一概使用class。
在C++中,關鍵字struct和class幾乎含義等同,我們為其人為添加語義,以便為定義的數據類型合理選擇使用哪個關鍵字。
struct被用在僅包含數據的消極對象(passive objects)上,可能包括有關聯的常量,但沒有存取數據成員之外的函數功能,而存取功能通過直接訪問實現而無需方法調用,這兒提到的方法是指只用於處理數據成員的,如構造函數、析構函數、Initialize()、Reset()、Validate()。
如果需要更多的函數功能,class更適合,如果不確定的話,直接使用class。
如果與STL結合,對於仿函數(functors)和特性(traits)可以不用class而是使用struct。
注意:類和結構體的成員變量使用不同的命名規則。
6. 繼承(Inheritance)
使用組合(composition,譯者注,這一點也是GoF在《Design Patterns》裡反復強調的)通常比使用繼承更適宜,如果使用繼承的話,只使用公共繼承。
定義:當子類繼承基類時,子類包含了父基類所有數據及操作的定義。C++實踐中,繼承主要用於兩種場合:實現繼承(implementation inheritance),子類繼承父類的實現代碼;接口繼承(interface inheritance),子類僅繼承父類的方法名稱。
優點:實現繼承通過原封不動的重用基類代碼減少了代碼量。由於繼承是編譯時聲明(compile-time declaration),編碼者和編譯器都可以理解相應操作並發現錯誤。接口繼承可用於程序上增強類的特定API的功能,在類沒有定義API的必要實現時,編譯器同樣可以偵錯。
缺點:對於實現繼承,由於實現子類的代碼在父類和子類間延展,要理解其實現變得更加困難。子類不能重寫父類的非虛函數,當然也就不能修改其實現。基類也可能定義了一些數據成員,還要區分基類的物理輪廓(physical layout)。
結論:
所有繼承必須是public的,如果想私有繼承的話,應該采取包含基類實例作為成員的方式作為替代。
不要過多使用實現繼承,組合通常更合適一些。努力做到只在“是一個”("is-a",譯者注,其他"has-a"情況下請使用組合)的情況下使用繼承:如果Bar的確“是一種”Foo,才令Bar是Foo的子類。
必要的話,令析構函數為virtual,必要是指,如果該類具有虛函數,其析構函數應該為虛函數。
譯者注:至於子類沒有額外數據成員,甚至父類也沒有任何數據成員的特殊情況下,析構函數的調用是否必要是語義爭論,從編程設計規范的角度看,在含有虛函數的父類中,定義虛析構函數絕對必要。
限定僅在子類訪問的成員函數為protected,需要注意的是數據成員應始終為私有。
當重定義派生的虛函數時,在派生類中明確聲明其為virtual。根本原因:如果遺漏virtual,閱讀者需要檢索類的所有祖先以確定該函數是否為虛函數(譯者注,雖然不影響其為虛函數的本質)。
7. 多重繼承(Multiple Inheritance)
真正需要用到多重實現繼承(multiple implementation inheritance)的時候非常少,只有當最多一個基類中含有實現,其他基類都是以Interface為後綴的純接口類時才會使用多重繼承。
定義:多重繼承允許子類擁有多個基類,要將作為純接口的基類和具有實現的基類區別開來。
優點:相比單繼承,多重實現繼承可令你重用更多代碼。
缺點:真正需要用到多重實現繼承的時候非常少,多重實現繼承看上去是不錯的解決方案,通常可以找到更加明確、清晰的、不同的解決方案。
結論:只有當所有超類(superclass)除第一個外都是純接口時才能使用多重繼承。為確保它們是純接口,這些類必須以Interface為後綴。
注意:關於此規則,Windows下有種例外情況(譯者注,將在本譯文最後一篇的規則例外中闡述)。

8. 接口(Interface)
接口是指滿足特定條件的類,這些類以Interface為後綴(非必需)。
定義:當一個類滿足以下要求時,稱之為純接口:
1) 只有純虛函數("=0")和靜態函數(下文提到的析構函數除外);
2) 沒有非靜態數據成員;
3) 沒有定義任何構造函數。如果有,也不含參數,並且為protected;
4) 如果是子類,也只能繼承滿足上述條件並以Interface為後綴的類。
接口類不能被直接實例化,因為它聲明了純虛函數。為確保接口類的所有實現可被正確銷毀,必須為之聲明虛析構函數(作為第1條規則的例外,析構函數不能是純虛函數)。具體細節可參考Stroustrup的《The C++ Programming Language, 3rd edition》第12.4節。
優點:以Interface為後綴可令他人知道不能為該接口類增加實現函數或非靜態數據成員,這一點對於多重繼承尤其重要。另外,對於Ja

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved