Effective c++(筆記) 之 雜項討論

jsjliuyun發表於2014-06-07

看到了Effective c++的最後一章,最開始的那章---記憶體管理還沒搞清楚,準備那章搞清楚完也寫篇部落格,不管怎樣,有好的開始就應該讓它有個完美的結束,雜項討論這章是作者將那些分到哪章都不合適的就索性放到了最後討論,我看完後從中摘出自己認為重要的坐下筆記,如果能幫得到大家,那就更榮幸了哈!

1.當我們定義一個類時,編譯器會自動給我產生哪些成員函式?

解析:我們都知道,當我們定義類時,如果我們沒有定義某些成員函式的話,編譯器會總會給我們自動合成,這就是編譯器默默為我們完成和呼叫函式,這些函式主要有以下幾個

建構函式、解構函式、拷貝建構函式、賦值運算子、(non-const)取地址運算子和(const)取地址運算子這六個函式,其程式碼形式如下

如果我們定義一個空類,如下所示

//空類
class Empty{

};

其實這個Empty類相當於如下:

//空類
class Empty{
	//編譯器會在我們用到這些函式時,如果類中沒有定義,會呼叫自己生產的
	Empty();
	~Empty();
	Empty(const Empty &rhs);
	Empty& operator==(const Empty &rhs);
	Empty* operator&();
	const Empty* operator&() const;
};

下面就以個例子來說明這些編譯器默默為我們產生的函式什麼時候會正常工作什麼時候會失效

下面是個NameObejct的模板類

template <typename T>
class NameObject{
public:
	NameObject(const char *name , const T &value);
	NameObject(cosnt string &name , cosnt T &value);
private:
	string nameValue;
	T objectValue;
};

//當我們自己定義的類中有了編譯器為我們生成的函式,那麼它便不會給我們生產
//NameObject類有了建構函式,編譯器不會再給我們生成
NameObject<int> no_1("Smallest Prime Number" , 2);
//類中沒有拷貝建構函式,所以呼叫編譯器為我們產生的拷貝建構函式
NameObject<int> no_2(no_1);

由於我們的模板類中沒有定義拷貝建構函式,所以呼叫編譯器為我們產生的拷貝建構函式以no_1.nameValue 和 no_1.objectValue為初值來講no_2物件的成員進行初始化,因為nameValue成員是string型別,而string型別有自己的拷貝建構函式,所以呼叫string的拷貝建構函式進行復制,而objectValue是int型別的,而int型別是內建型別,所以只能bitwise一位一位的進行復制。

上面大致講述了,編譯器為我們產生的拷貝建構函式是怎麼工作的,下面就談談賦值運算子是怎麼工作的,

我們稍微改下上面的模板類,來演示一下,賦值運算子不能工作的情境。

template <typename T>
class NameObject{
public:
	NameObject(string name , const T &value);
private:
	string &nameValue;
	const T objectValue;
};

string newDog("xiaoa");
string oldDog("xiaob");
NameObject<int> p(newDog , 2);
NameObject<int> s(oldDog , 29);
p = s;

當指向p=s時,由於定義的模板類中沒有定義賦值運算子,所以採用編譯器為我們產生的賦值運算子函式

當執行賦值運算子時,p的資料成員的nameValue的引用應該繫結s.nameValue繫結的string物件,但是我們都知道定義引用時應該給予繫結的初始物件並且不能再更改,同時類中的另一個資料成員是const,不能被寫入,所以編譯器所產生的賦值運算子函式將會拒絕執行,如果想完成,必須自己定義賦值運算子函式。

對於上述這種情況,我們應該顯示的拒絕不能執行的函式,在前面的條款中提到這點,當類中不可以執行編譯器為我們默默生成的函式時,應該顯式的拒絕它,怎麼顯式的拒絕呢?其實看了前面條款的同學都知道,只要將這個函式private即可,如下所示:

template <typename T>
class NameObject{
public:
	NameObject(string &name , const T &value);
private:
	NameObject& operator=(const NameObject &rhs);
	string &nameValue;
	const T objectValue;
};

當然如果我們想實現上面的賦值運算子的功能,必然要自己寫程式碼實現,這樣當類中定義了賦值運算子後,編譯器也不會給我們產生預設的了。


2.寧願編譯連結時出錯,也不要再執行時出錯!

答:自我感覺這點沒什麼好講的,因為在編譯連結時出錯我們根據編譯器提示的錯誤查詢,一般情況下都可以搞定,然而,在執行期出錯的原因就很多,很難斷定,所以Effective c++就提出了,儘量讓錯誤都在編譯期顯現出來,也就是改變類的設計方法,新增新的型別來檢查,這僅僅是提供了思路,書上也給出了例子,但是感覺在實際的開發中很難去做,所以在此只是稍微提下記錄,有興趣的可以詳細看書哈!


3.在使用非區域性靜態物件之前要先確定它已被初始化。

答:什麼是非區域性靜態物件(non-local-static-object)呢?

定義於global 或者 namespace scope

在某個class 類中宣告為static

在某個檔案範圍內定義為static

還是舉個例子

class Test{

};
//非區域性靜態物件
Test test;

另外就要談初值的問題了,我們在寫程式的時候,往往會犯的錯誤,定義了變數沒有初始化就來用,這是大忌,在前面的條款中也提到過,儘量延遲變數的定義時間,最好在該變數初始化的時候去定義它。

非區域性靜態物件的問題在於,如果這個非區域性物件的初始化不依賴於其他物件的初始化,那麼沒什麼問題,或者說如果是單一的編譯單元的話也沒什麼問題,但是如果是不同的編譯單元,這個編譯單元中物件的初始化依賴另一個編譯單元的初始化時那麼此時就會產生問題,因為c++是支援分離的編譯單元(不同編譯單元的程式碼生成目標檔案,然後連結起來形成可執行檔案),因為c++的這種特性就會導致這種初始化動作時發生的問題。

如下例所示

class FileSystem{

};
FileSystem theFileSystem;//非區域性靜態物件

class Directory{
public:
	Directory();
};
Directory::Directory()
{
	//該建構函式中依賴theFileSystem的初始化
}
Directory tempDir;

如上面的情況,初始化順序的問題就顯現出來了,Directory建構函式初始化tempDir時依賴於theFileSystem的初始化,我們利用呼叫函式來解決,返回靜態物件的引用,這樣就一定保證已經初始化了。

class FileSystem{

};

FileSystem& theFileSystem()
{
	static FileSystem tfs;//定義並且初始化靜態物件
	return tfs;//傳回的引用指向tfs
}

class Directory{
public:
	Directory();
};
Directory::Directory()
{
	//該建構函式中依賴theFileSystem的初始化
}
Directory& tempDir
{
	static Directory td;
	return td;
};

這樣一來,物件的初始化順序就得到了合理的安排,這樣就會避免上面的情況。

相關文章