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

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

迴歸正題,這篇博客就大概的把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的做法

另外,使用抽象類的做法

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章