Effective c++(筆記)----類與函式之實現

jsjliuyun發表於2014-05-30

       上篇部落格中集中說明了在設計一個類的時候常遇到的問題,當然部落格中還夾雜著我隨時想到的一些知識,發現自己寫部落格沒很多人寫的好,可能是自己語言不會組織,要麼就是寫的東西大家不願意看,反正是有這方面的專業問題或者部落格中有什麼明顯的錯誤和問題,大家提出來,我也好改進哈!

迴歸正題,這篇部落格就大概的把Effective c++中類與函式這節看到的知識點做個筆記。

設計好一個類後,自己就要去實現這個類(實現類中的成員函式、友元、非成員函式等)

可能大家會遇到以下問題

1.在類的成員函式中,儘量避免返回內部資料的handles

答:起初看到這句話感覺就不是很懂,什麼叫內部資料的handles,其實說白了,就是儘量在成員函式中避免返回類的指標或者引用成員。

為什麼這麼說呢?

原因----當成員函式返回類的指標成員或者引用成員時,可能會破壞類的抽象性,或者破環const 成員函式的const性,特別是當涉及暫時物件(個人理解就是臨時物件),可能會造成懸浮指標也就是指標指向了已經釋放的記憶體空間-------野指標

感覺上面的話好抽象,於是就拿例子來詳細說明了

class String{
public:
	String(const char *value);
	~String();
	operator char * () const;
private:
	char *data;
};
String::operator char * () const
{
	return data;
}

上面的const成員函式可是型別轉換運算子,它沒有返回型別,沒有引數。

順便插句:c++中沒有返回型別的只有建構函式、解構函式和型別轉換運算子函式

這個函式實現的功能是可以將String型別轉換為char*型別的

如果定義了一個常量的String物件B,如下所示

const String B = "Hello World";
char *str = B; //呼叫了B.operator char * ()
strcpy(str , "Hi Mom");

當呼叫了String類中的型別轉換符,此時指標str也指向了物件B中指標data成員指向的記憶體空間,這樣由str直接就可以改變指標data成員指向的值,但是這就違背了物件B為const的性質,所以當函式中返回類的指標成員時,對於const的成員函式會破壞物件的const特性,那麼該怎麼處理呢?一種比較快比較安全的方法就是針對const物件和非const物件分別寫一個成員函式。如下所示

class String{
public:
	String(const char *value);
	~String();
	operator const char * () const;
	operator char * ();
private:
	char *data;
};
String::operator const char * () const
{
	return data;
}
String::operator char * ()
{
	return data;
}

這樣的話const物件呼叫物件的const成員函式且同時返回是const char*型別,這就使其不能通過str改變其指標指向的值(這裡的const是指指標指向的值為const----上篇部落格詳細討論過這個問題)

對於引用也是一樣的 , 如下例所示,對const和非const的分別處理

class String{
public:
	String(const char *value);
	~String();
	operator const char * () const;
	operator char * ();
	//取元素運算子過載
	const char& operator[](int index) const;
	char& operator[](int index);
private:
	char *data;
};

const char& String::operator[](int index) const
{
	return data[index];
}
char& String::operator[](int index)
{
	return data[index];
}

當成員函式非得返回類的指標成員和引用成員時,例如上述返回char&,對於內建型別不能返回char,只能返回char&,因為對於返回值是內建型的函式,修改返回值是絕對不合法的!應該對non-const和const版本的分別處理,除此之外儘量不要返回內部資料的handles。

另外是當涉及到臨時物件時會造成野指標的情況,如下例所示,對上面的類再新增一個非const的成員函式

String someFamousAuthor()
{
	if(...)
	{
		return "A";
	}else if(...)
	{
		return "B";
	}else{
		return "C";
	}
}

當同樣有指標指向該函式時,如

const char *pc = someFamousAuthor();

函式最後返回String物件,當該函式執行完其物件會呼叫解構函式,這個物件的指標成員data所指向的記憶體會被釋放,而此時指標pc仍然指向那塊記憶體區域,這就是指標指向了已經釋放的記憶體區域,也就是懸浮指標的情況,------野指標

2.在c++中定義變數時,儘可能的在能給予初值時才定義,這是為什麼呢?

答:如果你寫的程式碼較多,我相信你對這樣的情況深有體會,常常會有自己的變數沒有用到而受到編譯器的警告,這還不算什麼,最可恨的是,編譯器沒錯誤,但是執行每次的結果都不同,一直查bug,很長時間過去了,才知道原來是自己定義的變數沒有初始化就開始使用了,這。。。。沒辦法再往下說了,一定要謹記定義變數時就要給予初值,養成這個好習慣,肯定不會出錯。

當然,我們也想知道里面的原因,當定義變數沒有使用編譯器常常會有警告,此時,你就是定義了變數分配了記憶體,卻沒使用,你這不是糟蹋記憶體麼?!同時你這樣幹也影響了你程式的效率,變數的定義除了分配了對應的記憶體,還要呼叫預設建構函式,當程式結束時呼叫解構函式,這都浪費時間影響程式的效率。

結論-----儘量延緩變數的定義,當你真正需要用它並且在定義的時候能夠給予初值時再去定義它。

3.在函式內千萬不要返回區域性物件的引用和返回堆空間中的物件(也就是new產生的物件)

答:這個應該很容易理解,區域性物件的作用域在區域性,當函式執行完後,區域性物件會隨之析構,如果此時你返回了區域性物件的引用,引用就是別名,看到引用就要看它繫結的物件,此時區域性物件已經析構,那麼此時引用沒有繫結的物件。上篇部落格中好像也說了,對於函式返回值是物件object時,儘量不要返回物件的引用形式,因為那樣效率還沒有直接返回物件的高!對於內建型返回引用的效率可能還比較高。

當然,如果返回堆空間中的物件,也就是以new獲得的指標所指的物件,返回這個物件時,很容易造成記憶體洩漏,呼叫這個函式時,往往就應該想也是必須想的是,呼叫該函式的物件應該負責刪除new的記憶體,如果考慮不周,很容易造成記憶體洩漏,不要去嘗試造成記憶體洩漏的任何情況。

4.什麼時候應該在函式的前面加上inline關鍵字?

答:inline關鍵字是宣告該函式為行內函數,當呼叫行內函數時,行內函數的程式碼在呼叫處展開,通常是函式程式碼量比較少的函式,但是好像現在還是不知道怎麼使用,以及使用的標準。

inline函式背後的含義是對此函式的每一個呼叫動作都以函式程式碼取代之。雖然免除了呼叫函式的成本,但是這很明顯會增加目的碼的大小,當行內函數的程式碼量較大時,很容易造成程式程式碼膨脹現象,更糟的可能產生換頁行為,使程式的大部分時間都浪費在了換頁上面。所以對於程式碼較多的函式是不合適進行內聯化的。

如果你在函式前面加上了inline關鍵字,這並不表示,編譯器一定會將此函式內聯化,有可能沒有內聯成功也就是out of inline ,那麼此時對於這個未內聯成功的函式,當呼叫時編譯器會按照普通函式呼叫之。

在舊規則下,對未內聯成功的函式而言,當呼叫它時每個編譯單元都會產生這個函式的靜態副本,如果這個函式中有自己定義的靜態區域性變數,同時也會產生這個副本,也就是說在舊規則下,對於未內聯成功的函式會當成static函式,甚至是當定義了指向該函式的函式指標時,也就是對這個函式取地址,此時對於每個取地址的編譯單元也還是會產生這個函式的靜態副本,相反,在新規則下,不論牽扯到的編譯單元有多少個,只有一個未內聯成功的函式副本產生出來。

對於建構函式和解構函式往往不是行內函數的最佳選擇,也許你認為建構函式中什麼程式碼也沒有,正好合適內聯化,但是你只看到了表面沒有看到內在的執行機制,對於建構函式,它會初始化類中所有資料成員 ,如果初始化成員時的建構函式也是行內函數的話,那個該建構函式會包含好幾個行內函數,再且,當這個類是派生類的時候,它不僅需要構造自己的資料成員,首先它會構造自己直接基類的資料成員,這些都是需要執行的,所以對於建構函式和解構函式而言是不適合作為行內函數的。

對於函式,在開始的時候儘量不要定義為內聯,當自己確定找到了那些佔重要效率地位的函式,且程式碼量確實很少,立即將其內聯化會有助於提高自己的程式效率。

5.怎麼將檔案之間的編譯依賴關係降至最低來提高程式重編譯的效率呢?

答:首先說為什麼這麼幹,因為如果檔案之間的關係太緊密,如這個類中包含了好幾個類的.h檔案,那麼就會導致我們改變任何一個.h檔案的任何一個小的地方,就會導致整個程式重新編譯,那麼在編譯過程就會浪費很長的時間。

其實這些都是可以避免的,有兩種方法可以達到這種效果降低檔案之間的依賴關係。

首先,儘量以class宣告取代class定義,成為Handle class的做法

另外,使用抽象類的做法

說實話,這兩種方法有些沒看懂,可能是自己的基礎不好,還需要再研究一下,等什麼時候透徹了,將對這部分的理解和例子新增進來哈!

相關文章